Extract SharedPreferences interaction from QSHost

In the new pipeline, use a separate repository to determine/set whether
custom tiles have been added for a given user.

Flag: QS_PIPELINE_NEW_HOST
Test: atest com.android.systemui.qs
Test: manual, add/remove tiles and see the file change
Fixes: 275737462
Change-Id: I94865574c749368bcbb1f1d8743ed8431a25a1e8
This commit is contained in:
Fabián Kozynski
2023-03-29 14:09:13 -04:00
parent d01cc57a5e
commit eaa14e0dbb
8 changed files with 280 additions and 21 deletions

View File

@@ -26,6 +26,7 @@ import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -34,7 +35,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public interface QSHost extends PanelInteractor {
public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
String TILES_SETTING = Settings.Secure.QS_TILES;
int POSITION_AT_END = -1;
@@ -102,9 +103,6 @@ public interface QSHost extends PanelInteractor {
void removeTileByUser(ComponentName tile);
void changeTilesByUser(List<String> previousTiles, List<String> newTiles);
boolean isTileAdded(ComponentName componentName, int userId);
void setTileAdded(ComponentName componentName, int userId, boolean added);
int indexOf(String tileSpec);
InstanceId getNewInstanceId();

View File

@@ -20,6 +20,8 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
import dagger.Binds
@@ -46,5 +48,19 @@ interface QSHostModule {
qsHost
}
}
@Provides
@JvmStatic
fun provideCustomTileAddedRepository(
featureFlags: FeatureFlags,
qsHost: QSHost,
customTileAddedRepository: CustomTileAddedSharedPrefsRepository
): CustomTileAddedRepository {
return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
customTileAddedRepository
} else {
qsHost
}
}
}
}

View File

@@ -33,6 +33,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
import java.util.List;
@@ -59,6 +60,7 @@ public class TileServiceManager {
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
private final UserTracker mUserTracker;
private final CustomTileAddedRepository mCustomTileAddedRepository;
private boolean mBindRequested;
private boolean mBindAllowed;
private boolean mBound;
@@ -72,9 +74,10 @@ public class TileServiceManager {
private boolean mStarted = false;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
tileServices.getContext(), tileServices,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
CustomTileAddedRepository customTileAddedRepository) {
this(tileServices, handler, userTracker, customTileAddedRepository,
new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
new Intent(TileService.ACTION_QS_TILE).setComponent(component),
userTracker.getUserHandle()));
@@ -82,11 +85,13 @@ public class TileServiceManager {
@VisibleForTesting
TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
CustomTileAddedRepository customTileAddedRepository,
TileLifecycleManager tileLifecycleManager) {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
mUserTracker = userTracker;
mCustomTileAddedRepository = customTileAddedRepository;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -111,8 +116,8 @@ public class TileServiceManager {
mStarted = true;
ComponentName component = mStateManager.getComponent();
final int userId = mStateManager.getUserId();
if (!mServices.getHost().isTileAdded(component, userId)) {
mServices.getHost().setTileAdded(component, userId, true);
if (!mCustomTileAddedRepository.isTileAdded(component, userId)) {
mCustomTileAddedRepository.setTileAdded(component, userId, true);
mStateManager.onTileAdded();
mStateManager.flushMessagesAndUnbind();
}

View File

@@ -41,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
@@ -77,6 +78,7 @@ public class TileServices extends IQSService.Stub {
private final UserTracker mUserTracker;
private final StatusBarIconController mStatusBarIconController;
private final PanelInteractor mPanelInteractor;
private final CustomTileAddedRepository mCustomTileAddedRepository;
private int mMaxBound = DEFAULT_MAX_BOUND;
@@ -89,7 +91,8 @@ public class TileServices extends IQSService.Stub {
KeyguardStateController keyguardStateController,
CommandQueue commandQueue,
StatusBarIconController statusBarIconController,
PanelInteractor panelInteractor) {
PanelInteractor panelInteractor,
CustomTileAddedRepository customTileAddedRepository) {
mHost = host;
mKeyguardStateController = keyguardStateController;
mContext = mHost.getContext();
@@ -101,6 +104,7 @@ public class TileServices extends IQSService.Stub {
mStatusBarIconController = statusBarIconController;
mCommandQueue.addCallback(mRequestListeningCallback);
mPanelInteractor = panelInteractor;
mCustomTileAddedRepository = customTileAddedRepository;
}
public Context getContext() {
@@ -128,7 +132,7 @@ public class TileServices extends IQSService.Stub {
protected TileServiceManager onCreateTileService(ComponentName component,
BroadcastDispatcher broadcastDispatcher) {
return new TileServiceManager(this, mHandlerProvider.get(), component,
broadcastDispatcher, mUserTracker);
broadcastDispatcher, mUserTracker, mCustomTileAddedRepository);
}
public void freeService(CustomTile tile, TileServiceManager service) {

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs.pipeline.data.repository
import android.content.ComponentName
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserFileManager
import javax.inject.Inject
/**
* Repository for keeping track of whether a given [CustomTile] [ComponentName] has been added to
* the set of current tiles for a user. This is used to determine when lifecycle methods in
* `TileService` about the tile being added/removed need to be called.
*/
interface CustomTileAddedRepository {
/**
* Check if a particular [CustomTile] associated with [componentName] has been added for
* [userId] and has not been removed since.
*/
fun isTileAdded(componentName: ComponentName, userId: Int): Boolean
/**
* Persists whether a particular [CustomTile] associated with [componentName] has been added and
* it's currently in the set of selected tiles for [userId].
*/
fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean)
}
@SysUISingleton
class CustomTileAddedSharedPrefsRepository
@Inject
constructor(private val userFileManager: UserFileManager) : CustomTileAddedRepository {
override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
return userFileManager
.getSharedPreferences(TILES, 0, userId)
.getBoolean(componentName.flattenToString(), false)
}
override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
userFileManager
.getSharedPreferences(TILES, 0, userId)
.edit()
.putBoolean(componentName.flattenToString(), added)
.apply()
}
companion object {
private const val TILES = "tiles_prefs"
}
}

View File

@@ -40,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
import org.junit.After;
@@ -64,6 +65,8 @@ public class TileServiceManagerTest extends SysuiTestCase {
private QSHost mQSHost;
@Mock
private Context mMockContext;
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
private HandlerThread mThread;
private Handler mHandler;
@@ -86,8 +89,9 @@ public class TileServiceManagerTest extends SysuiTestCase {
mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
mTileLifecycle);
mCustomTileAddedRepository, mTileLifecycle);
}
@After
@@ -98,28 +102,34 @@ public class TileServiceManagerTest extends SysuiTestCase {
@Test
public void testSetTileAddedIfNotAdded() {
when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
.thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
verify(mQSHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
verify(mCustomTileAddedRepository)
.setTileAdded(mComponentName, mUserTracker.getUserId(), true);
}
@Test
public void testNotSetTileAddedIfAdded() {
when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
.thenReturn(true);
mTileServiceManager.startLifecycleManagerAndAddTile();
verify(mQSHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
verify(mCustomTileAddedRepository, never())
.setTileAdded(eq(mComponentName), anyInt(), eq(true));
}
@Test
public void testSetTileAddedCorrectUser() {
int user = 10;
when(mUserTracker.getUserId()).thenReturn(user);
when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
.thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
verify(mQSHost).setTileAdded(mComponentName, user, true);
verify(mCustomTileAddedRepository)
.setTileAdded(mComponentName, user, true);
}
@Test

View File

@@ -42,6 +42,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
@@ -98,6 +99,8 @@ public class TileServicesTest extends SysuiTestCase {
private PanelInteractor mPanelInteractor;
@Captor
private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksArgumentCaptor;
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
@Before
public void setUp() throws Exception {
@@ -115,7 +118,7 @@ public class TileServicesTest extends SysuiTestCase {
mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
mPanelInteractor);
mPanelInteractor, mCustomTileAddedRepository);
}
@After
@@ -293,9 +296,11 @@ public class TileServicesTest extends SysuiTestCase {
TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
KeyguardStateController keyguardStateController, CommandQueue commandQueue,
StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) {
StatusBarIconController statusBarIconController, PanelInteractor panelInteractor,
CustomTileAddedRepository customTileAddedRepository) {
super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
commandQueue, statusBarIconController, panelInteractor);
commandQueue, statusBarIconController, panelInteractor,
customTileAddedRepository);
}
@Override

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs.pipeline.data.repository
import android.content.ComponentName
import android.content.SharedPreferences
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserFileManager
import com.android.systemui.util.FakeSharedPreferences
import com.google.common.truth.Truth.assertThat
import java.io.File
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
private lateinit var underTest: CustomTileAddedSharedPrefsRepository
@Test
fun setTileAdded_inSharedPreferences() {
val userId = 0
val sharedPrefs = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
underTest.setTileAdded(TEST_COMPONENT, userId, added = false)
assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isFalse()
}
@Test
fun setTileAdded_differentComponents() {
val userId = 0
val sharedPrefs = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
assertThat(sharedPrefs.getForComponentName(OTHER_TEST_COMPONENT)).isFalse()
}
@Test
fun setTileAdded_differentUsers() {
val sharedPrefs0 = FakeSharedPreferences()
val sharedPrefs1 = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
underTest.setTileAdded(TEST_COMPONENT, userId = 1, added = true)
assertThat(sharedPrefs0.getForComponentName(TEST_COMPONENT)).isFalse()
assertThat(sharedPrefs1.getForComponentName(TEST_COMPONENT)).isTrue()
}
@Test
fun isTileAdded_fromSharedPreferences() {
val userId = 0
val sharedPrefs = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
sharedPrefs.setForComponentName(TEST_COMPONENT, true)
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isTrue()
sharedPrefs.setForComponentName(TEST_COMPONENT, false)
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
}
@Test
fun isTileAdded_differentComponents() {
val userId = 0
val sharedPrefs = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
sharedPrefs.setForComponentName(OTHER_TEST_COMPONENT, true)
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
assertThat(underTest.isTileAdded(OTHER_TEST_COMPONENT, userId)).isTrue()
}
@Test
fun isTileAdded_differentUsers() {
val sharedPrefs0 = FakeSharedPreferences()
val sharedPrefs1 = FakeSharedPreferences()
val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
sharedPrefs1.setForComponentName(TEST_COMPONENT, true)
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 0)).isFalse()
assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 1)).isTrue()
}
private fun SharedPreferences.getForComponentName(componentName: ComponentName): Boolean {
return getBoolean(componentName.flattenToString(), false)
}
private fun SharedPreferences.setForComponentName(
componentName: ComponentName,
value: Boolean
) {
edit().putBoolean(componentName.flattenToString(), value).commit()
}
companion object {
private val TEST_COMPONENT = ComponentName("pkg", "cls")
private val OTHER_TEST_COMPONENT = ComponentName("pkg", "other")
}
}
private const val FILE_NAME = "tiles_prefs"
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
UserFileManager {
override fun getFile(fileName: String, userId: Int): File {
throw UnsupportedOperationException()
}
override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
if (fileName != FILE_NAME) {
throw IllegalArgumentException("Preference files must be $FILE_NAME")
}
return sharedPrefs.getValue(userId)
}
}