Merge changes I966312e0,Ie443f626 into tm-qpr-dev am: a96f8dd380

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19396349

Change-Id: I1394eee4ddd6a003b04fbc156ce318c67622f9b4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 77652c9..ac46c85 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,6 +29,7 @@
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.UiEventLogger;
@@ -47,6 +48,7 @@
 import com.android.systemui.qs.external.TileServiceKey;
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -88,6 +90,10 @@
     public static final int POSITION_AT_END = -1;
     public static final String TILES_SETTING = Secure.QS_TILES;
 
+    // Shared prefs that hold tile lifecycle info.
+    @VisibleForTesting
+    static final String TILES = "tiles_prefs";
+
     private final Context mContext;
     private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
     protected final ArrayList<String> mTileSpecs = new ArrayList<>();
@@ -99,6 +105,7 @@
     private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
     private final Executor mMainExecutor;
+    private final UserFileManager mUserFileManager;
 
     private final List<Callback> mCallbacks = new ArrayList<>();
     @Nullable
@@ -135,7 +142,8 @@
             SecureSettings secureSettings,
             CustomTileStatePersister customTileStatePersister,
             TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
-            TileLifecycleManager.Factory tileLifecycleManagerFactory
+            TileLifecycleManager.Factory tileLifecycleManagerFactory,
+            UserFileManager userFileManager
     ) {
         mIconController = iconController;
         mContext = context;
@@ -148,6 +156,7 @@
         mMainExecutor = mainExecutor;
         mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
         mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
+        mUserFileManager = userFileManager;
 
         mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
         mCentralSurfacesOptional = centralSurfacesOptional;
@@ -392,6 +401,11 @@
      */
     @Override
     public void removeTile(String spec) {
+        if (spec.startsWith(CustomTile.PREFIX)) {
+            // If the tile is removed (due to it not actually existing), mark it as removed. That
+            // way it will be marked as newly added if it appears in the future.
+            setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
+        }
         mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
     }
 
@@ -515,7 +529,7 @@
                 lifecycleManager.onStopListening();
                 lifecycleManager.onTileRemoved();
                 mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
-                TileLifecycleManager.setTileAdded(mContext, component, false);
+                setTileAdded(component, mCurrentUser, false);
                 lifecycleManager.flushMessagesAndUnbind();
             }
         }
@@ -552,6 +566,36 @@
         throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
     }
 
+    /**
+     * Check if a particular {@link CustomTile} has been added for a user and has not been removed
+     * since.
+     * @param componentName the {@link ComponentName} of the
+     *                      {@link android.service.quicksettings.TileService} associated with the
+     *                      tile.
+     * @param userId the user to check
+     */
+    public boolean isTileAdded(ComponentName componentName, int userId) {
+        return mUserFileManager
+                .getSharedPreferences(TILES, 0, userId)
+                .getBoolean(componentName.flattenToString(), false);
+    }
+
+    /**
+     * Persists whether a particular {@link CustomTile} has been added and it's currently in the
+     * set of selected tiles ({@link #mTiles}.
+     * @param componentName the {@link ComponentName} of the
+     *                      {@link android.service.quicksettings.TileService} associated
+     *                      with the tile.
+     * @param userId the user for this tile
+     * @param added {@code true} if the tile is being added, {@code false} otherwise
+     */
+    public void setTileAdded(ComponentName componentName, int userId, boolean added) {
+        mUserFileManager.getSharedPreferences(TILES, 0, userId)
+                .edit()
+                .putBoolean(componentName.flattenToString(), added)
+                .apply();
+    }
+
     protected static List<String> loadTileSpecs(Context context, String tileList) {
         final Resources res = context.getResources();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index a49d3fd..3e445dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -127,6 +127,10 @@
         TileLifecycleManager create(Intent intent, UserHandle userHandle);
     }
 
+    public int getUserId() {
+        return mUser.getIdentifier();
+    }
+
     public ComponentName getComponent() {
         return mIntent.getComponent();
     }
@@ -507,13 +511,4 @@
     public interface TileChangeListener {
         void onTileChanged(ComponentName tile);
     }
-
-    public static boolean isTileAdded(Context context, ComponentName component) {
-        return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
-    }
-
-    public static void setTileAdded(Context context, ComponentName component, boolean added) {
-        context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
-                added).commit();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfc57db..e86bd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -109,9 +109,9 @@
     void startLifecycleManagerAndAddTile() {
         mStarted = true;
         ComponentName component = mStateManager.getComponent();
-        Context context = mServices.getContext();
-        if (!TileLifecycleManager.isTileAdded(context, component)) {
-            TileLifecycleManager.setTileAdded(context, component, true);
+        final int userId = mStateManager.getUserId();
+        if (!mServices.getHost().isTileAdded(component, userId)) {
+            mServices.getHost().setTileAdded(component, userId, true);
             mStateManager.onTileAdded();
             mStateManager.flushMessagesAndUnbind();
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 7dbc561..3c58b6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -22,6 +22,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
@@ -32,11 +33,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
+import android.util.SparseArray;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -60,12 +63,14 @@
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.FakeSharedPreferences;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
@@ -76,6 +81,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -130,6 +136,10 @@
     private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
     @Mock
     private TileLifecycleManager mTileLifecycleManager;
+    @Mock
+    private UserFileManager mUserFileManager;
+
+    private SparseArray<SharedPreferences> mSharedPreferencesByUser;
 
     private FakeExecutor mMainExecutor;
 
@@ -140,17 +150,29 @@
         MockitoAnnotations.initMocks(this);
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
+        mSharedPreferencesByUser = new SparseArray<>();
+
         when(mTileServiceRequestControllerBuilder.create(any()))
                 .thenReturn(mTileServiceRequestController);
         when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
                 .thenReturn(mTileLifecycleManager);
+        when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+                .thenAnswer((Answer<SharedPreferences>) invocation -> {
+                    assertEquals(QSTileHost.TILES, invocation.getArgument(0));
+                    int userId = invocation.getArgument(2);
+                    if (!mSharedPreferencesByUser.contains(userId)) {
+                        mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
+                    }
+                    return mSharedPreferencesByUser.get(userId);
+                });
 
         mSecureSettings = new FakeSettings();
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
                 mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
-                mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+                mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
+                mUserFileManager);
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
             @Override
@@ -528,6 +550,118 @@
         assertEquals("spec1", getSetting());
     }
 
+    @Test
+    public void testIsTileAdded_true() {
+        int user = mUserTracker.getUserId();
+        getSharedPreferenecesForUser(user)
+                .edit()
+                .putBoolean(CUSTOM_TILE.flattenToString(), true)
+                .apply();
+
+        assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+    }
+
+    @Test
+    public void testIsTileAdded_false() {
+        int user = mUserTracker.getUserId();
+        getSharedPreferenecesForUser(user)
+                .edit()
+                .putBoolean(CUSTOM_TILE.flattenToString(), false)
+                .apply();
+
+        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+    }
+
+    @Test
+    public void testIsTileAdded_notSet() {
+        int user = mUserTracker.getUserId();
+
+        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+    }
+
+    @Test
+    public void testIsTileAdded_differentUser() {
+        int user = mUserTracker.getUserId();
+        mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
+                .edit()
+                .putBoolean(CUSTOM_TILE.flattenToString(), true)
+                .apply();
+
+        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
+    }
+
+    @Test
+    public void testSetTileAdded_true() {
+        int user = mUserTracker.getUserId();
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+        assertTrue(getSharedPreferenecesForUser(user)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    @Test
+    public void testSetTileAdded_false() {
+        int user = mUserTracker.getUserId();
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
+
+        assertFalse(getSharedPreferenecesForUser(user)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    @Test
+    public void testSetTileAdded_differentUser() {
+        int user = mUserTracker.getUserId();
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+        assertFalse(getSharedPreferenecesForUser(user + 1)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    @Test
+    public void testSetTileRemoved_afterCustomTileChangedByUser() {
+        int user = mUserTracker.getUserId();
+        saveSetting(CUSTOM_TILE_SPEC);
+
+        // This will be done by TileServiceManager
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+        mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1"));
+        assertFalse(getSharedPreferenecesForUser(user)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    @Test
+    public void testSetTileRemoved_removedByUser() {
+        int user = mUserTracker.getUserId();
+        saveSetting(CUSTOM_TILE_SPEC);
+
+        // This will be done by TileServiceManager
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+        mQSTileHost.removeTileByUser(CUSTOM_TILE);
+        mMainExecutor.runAllReady();
+        assertFalse(getSharedPreferenecesForUser(user)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    @Test
+    public void testSetTileRemoved_removedBySystem() {
+        int user = mUserTracker.getUserId();
+        saveSetting("spec1" + CUSTOM_TILE_SPEC);
+
+        // This will be done by TileServiceManager
+        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+        mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
+        mMainExecutor.runAllReady();
+        assertFalse(getSharedPreferenecesForUser(user)
+                .getBoolean(CUSTOM_TILE.flattenToString(), false));
+    }
+
+    private SharedPreferences getSharedPreferenecesForUser(int user) {
+        return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
+    }
+
     private class TestQSTileHost extends QSTileHost {
         TestQSTileHost(Context context, StatusBarIconController iconController,
                 QSFactory defaultFactory, Executor mainExecutor,
@@ -537,11 +671,13 @@
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
-                TileLifecycleManager.Factory tileLifecycleManagerFactory) {
+                TileLifecycleManager.Factory tileLifecycleManagerFactory,
+                UserFileManager userFileManager) {
             super(context, iconController, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
                     uiEventLogger, userTracker, secureSettings, customTileStatePersister,
-                    tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
+                    tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
+                    userFileManager);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 573980d..8aa625a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -19,6 +19,14 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -31,6 +39,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.UserTracker;
 
 import org.junit.After;
@@ -38,37 +47,45 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TileServiceManagerTest extends SysuiTestCase {
 
+    @Mock
     private TileServices mTileServices;
+    @Mock
     private TileLifecycleManager mTileLifecycle;
+    @Mock
+    private UserTracker mUserTracker;
+    @Mock
+    private QSTileHost mQSTileHost;
+    @Mock
+    private Context mMockContext;
+
     private HandlerThread mThread;
     private Handler mHandler;
     private TileServiceManager mTileServiceManager;
-    private UserTracker mUserTracker;
-    private Context mMockContext;
+    private ComponentName mComponentName;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mThread = new HandlerThread("TestThread");
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
-        mTileServices = Mockito.mock(TileServices.class);
-        mUserTracker = Mockito.mock(UserTracker.class);
-        Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
-        Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+        when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
 
-        mMockContext = Mockito.mock(Context.class);
-        Mockito.when(mTileServices.getContext()).thenReturn(mMockContext);
-        mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
-        Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
-        ComponentName componentName = new ComponentName(mContext,
-                TileServiceManagerTest.class);
-        Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
+        when(mTileServices.getContext()).thenReturn(mMockContext);
+        when(mTileServices.getHost()).thenReturn(mQSTileHost);
+        when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
+        when(mTileLifecycle.isActiveTile()).thenReturn(false);
+
+        mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
+        when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
         mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
                 mTileLifecycle);
     }
@@ -80,17 +97,44 @@
     }
 
     @Test
+    public void testSetTileAddedIfNotAdded() {
+        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+
+        verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+    }
+
+    @Test
+    public void testNotSetTileAddedIfAdded() {
+        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+
+        verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+    }
+
+    @Test
+    public void testSetTileAddedCorrectUser() {
+        int user = 10;
+        when(mUserTracker.getUserId()).thenReturn(user);
+        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+
+        verify(mQSTileHost).setTileAdded(mComponentName, user, true);
+    }
+
+    @Test
     public void testUninstallReceiverExported() {
+        mTileServiceManager.startLifecycleManagerAndAddTile();
         ArgumentCaptor<IntentFilter> intentFilterCaptor =
                 ArgumentCaptor.forClass(IntentFilter.class);
 
-        Mockito.verify(mMockContext).registerReceiverAsUser(
-                Mockito.any(),
-                Mockito.any(),
+        verify(mMockContext).registerReceiverAsUser(
+                any(),
+                any(),
                 intentFilterCaptor.capture(),
-                Mockito.any(),
-                Mockito.any(),
-                Mockito.eq(Context.RECEIVER_EXPORTED)
+                any(),
+                any(),
+                eq(Context.RECEIVER_EXPORTED)
         );
         IntentFilter filter = intentFilterCaptor.getValue();
         assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
@@ -99,38 +143,41 @@
 
     @Test
     public void testSetBindRequested() {
+        mTileServiceManager.startLifecycleManagerAndAddTile();
         // Request binding.
         mTileServiceManager.setBindRequested(true);
         mTileServiceManager.setLastUpdate(0);
         mTileServiceManager.calculateBindPriority(5);
-        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+        verify(mTileServices, times(2)).recalculateBindAllowance();
         assertEquals(5, mTileServiceManager.getBindPriority());
 
         // Verify same state doesn't trigger recalculating for no reason.
         mTileServiceManager.setBindRequested(true);
-        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+        verify(mTileServices, times(2)).recalculateBindAllowance();
 
         mTileServiceManager.setBindRequested(false);
         mTileServiceManager.calculateBindPriority(5);
-        Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance();
+        verify(mTileServices, times(3)).recalculateBindAllowance();
         assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());
     }
 
     @Test
     public void testPendingClickPriority() {
-        Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+        when(mTileLifecycle.hasPendingClick()).thenReturn(true);
         mTileServiceManager.calculateBindPriority(0);
         assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());
     }
 
     @Test
     public void testBind() {
+        mTileServiceManager.startLifecycleManagerAndAddTile();
         // Trigger binding requested and allowed.
         mTileServiceManager.setBindRequested(true);
         mTileServiceManager.setBindAllowed(true);
 
         ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
-        Mockito.verify(mTileLifecycle, Mockito.times(1)).setBindService(captor.capture());
+        verify(mTileLifecycle, times(1)).setBindService(captor.capture());
         assertTrue((boolean) captor.getValue());
 
         mTileServiceManager.setBindRequested(false);
@@ -141,7 +188,7 @@
 
         mTileServiceManager.setBindAllowed(false);
         captor = ArgumentCaptor.forClass(Boolean.class);
-        Mockito.verify(mTileLifecycle, Mockito.times(2)).setBindService(captor.capture());
+        verify(mTileLifecycle, times(2)).setBindService(captor.capture());
         assertFalse((boolean) captor.getValue());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 471ddfd..213eca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
@@ -118,6 +119,8 @@
     private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
     @Mock
     private TileLifecycleManager mTileLifecycleManager;
+    @Mock
+    private UserFileManager mUserFileManager;
 
     @Before
     public void setUp() throws Exception {
@@ -149,7 +152,8 @@
                 mSecureSettings,
                 mock(CustomTileStatePersister.class),
                 mTileServiceRequestControllerBuilder,
-                mTileLifecycleManagerFactory);
+                mTileLifecycleManagerFactory,
+                mUserFileManager);
         mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
                 mUserTracker, mKeyguardStateController, mCommandQueue);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
new file mode 100644
index 0000000..d886ffd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 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.util
+
+import android.content.SharedPreferences
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FakeSharedPreferencesTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var listener: SharedPreferences.OnSharedPreferenceChangeListener
+
+    private lateinit var sharedPreferences: SharedPreferences
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        sharedPreferences = FakeSharedPreferences()
+    }
+
+    @Test
+    fun testGetString_default() {
+        val default = "default"
+        val result = sharedPreferences.getString("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testGetStringSet_default() {
+        val default = setOf("one", "two")
+        val result = sharedPreferences.getStringSet("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testGetInt_default() {
+        val default = 10
+        val result = sharedPreferences.getInt("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testGetLong_default() {
+        val default = 11L
+        val result = sharedPreferences.getLong("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testGetFloat_default() {
+        val default = 1.3f
+        val result = sharedPreferences.getFloat("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testGetBoolean_default() {
+        val default = true
+        val result = sharedPreferences.getBoolean("key", default)
+        assertThat(result).isEqualTo(default)
+    }
+
+    @Test
+    fun testPutValuesAndRetrieve() {
+        val editor = sharedPreferences.edit()
+        val data = listOf<Data<*>>(
+            Data(
+                "keyString",
+                "value",
+                SharedPreferences.Editor::putString,
+                { getString(it, "") }
+            ),
+            Data(
+                "keyStringSet",
+                setOf("one", "two"),
+                SharedPreferences.Editor::putStringSet,
+                { getStringSet(it, emptySet()) }
+            ),
+            Data("keyInt", 10, SharedPreferences.Editor::putInt, { getInt(it, 0) }),
+            Data("keyLong", 11L, SharedPreferences.Editor::putLong, { getLong(it, 0L) }),
+            Data(
+                "keyFloat",
+                1.3f,
+                SharedPreferences.Editor::putFloat,
+                { getFloat(it, 0f) }
+            ),
+            Data(
+                "keyBoolean",
+                true,
+                SharedPreferences.Editor::putBoolean,
+                { getBoolean(it, false) }
+            )
+        )
+
+        data.fold(editor) { ed, d ->
+            d.set(ed)
+        }
+        editor.commit()
+
+        data.forEach {
+            assertThat(it.get(sharedPreferences)).isEqualTo(it.value)
+        }
+    }
+
+    @Test
+    fun testContains() {
+        sharedPreferences.edit().putInt("key", 10).commit()
+
+        assertThat(sharedPreferences.contains("key")).isTrue()
+        assertThat(sharedPreferences.contains("other")).isFalse()
+    }
+
+    @Test
+    fun testOverwrite() {
+        sharedPreferences.edit().putInt("key", 10).commit()
+        sharedPreferences.edit().putInt("key", 11).commit()
+
+        assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+    }
+
+    @Test
+    fun testDeleteString() {
+        sharedPreferences.edit().putString("key", "value").commit()
+        sharedPreferences.edit().putString("key", null).commit()
+
+        assertThat(sharedPreferences.contains("key")).isFalse()
+    }
+
+    @Test
+    fun testDeleteAndReplaceString() {
+        sharedPreferences.edit().putString("key", "value").commit()
+        sharedPreferences.edit().putString("key", "other").putString("key", null).commit()
+
+        assertThat(sharedPreferences.getString("key", "")).isEqualTo("other")
+    }
+
+    @Test
+    fun testDeleteStringSet() {
+        sharedPreferences.edit().putStringSet("key", setOf("one")).commit()
+        sharedPreferences.edit().putStringSet("key", setOf("two")).commit()
+
+        assertThat(sharedPreferences.getStringSet("key", emptySet())).isEqualTo(setOf("two"))
+    }
+
+    @Test
+    fun testClear() {
+        sharedPreferences.edit().putInt("keyInt", 1).putString("keyString", "a").commit()
+        sharedPreferences.edit().clear().commit()
+
+        assertThat(sharedPreferences.contains("keyInt")).isFalse()
+        assertThat(sharedPreferences.contains("keyString")).isFalse()
+    }
+
+    @Test
+    fun testClearAndWrite() {
+        sharedPreferences.edit().putInt("key", 10).commit()
+        sharedPreferences.edit().putInt("key", 11).clear().commit()
+
+        assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+    }
+
+    @Test
+    fun testListenerNotifiedOnChanges() {
+        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+        sharedPreferences.edit().putInt("keyInt", 10).putString("keyString", "value").commit()
+
+        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyInt")
+        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+        verifyNoMoreInteractions(listener)
+    }
+
+    @Test
+    fun testListenerNotifiedOnClear() {
+        sharedPreferences.edit().putInt("keyInt", 10).commit()
+        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+        sharedPreferences.edit().clear().commit()
+
+        verify(listener).onSharedPreferenceChanged(sharedPreferences, null)
+        verifyNoMoreInteractions(listener)
+    }
+
+    @Test
+    fun testListenerNotifiedOnRemoval() {
+        sharedPreferences.edit()
+            .putString("keyString", "a")
+            .putStringSet("keySet", setOf("a"))
+            .commit()
+
+        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+        sharedPreferences.edit().putString("keyString", null).putStringSet("keySet", null).commit()
+
+        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keySet")
+        verifyNoMoreInteractions(listener)
+    }
+
+    @Test
+    fun testListenerUnregistered() {
+        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+        sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
+        sharedPreferences.edit().putInt("key", 10).commit()
+
+        verify(listener, never()).onSharedPreferenceChanged(eq(sharedPreferences), anyString())
+    }
+
+    @Test
+    fun testSharedPreferencesOnlyModifiedOnCommit() {
+        sharedPreferences.edit().putInt("key", 10)
+
+        assertThat(sharedPreferences.contains("key")).isFalse()
+    }
+
+    private data class Data<T>(
+        val key: String,
+        val value: T,
+        private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor,
+        private val getter: SharedPreferences.(String) -> T
+    ) {
+        fun set(editor: SharedPreferences.Editor): SharedPreferences.Editor {
+            return editor.setter(key, value)
+        }
+
+        fun get(sharedPreferences: SharedPreferences): T {
+            return sharedPreferences.getter(key)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
new file mode 100644
index 0000000..4a881a7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 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.util
+
+import android.content.SharedPreferences
+
+/**
+ * Fake [SharedPreferences] to use within tests
+ *
+ * This will act in the same way as a real one for a particular file, but will store all the
+ * data in memory in the instance.
+ *
+ * [SharedPreferences.Editor.apply] and [SharedPreferences.Editor.commit] both act in the same way,
+ * synchronously modifying the stored data. Listeners are dispatched in the same thread, also
+ * synchronously.
+ */
+class FakeSharedPreferences : SharedPreferences {
+    private val data = mutableMapOf<String, Any>()
+    private val listeners = mutableSetOf<SharedPreferences.OnSharedPreferenceChangeListener>()
+
+    override fun getAll(): Map<String, *> {
+        return data
+    }
+
+    override fun getString(key: String, defValue: String?): String? {
+        return data.getOrDefault(key, defValue) as? String?
+    }
+
+    override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
+        return data.getOrDefault(key, defValues) as? MutableSet<String>?
+    }
+
+    override fun getInt(key: String, defValue: Int): Int {
+        return data.getOrDefault(key, defValue) as Int
+    }
+
+    override fun getLong(key: String, defValue: Long): Long {
+        return data.getOrDefault(key, defValue) as Long
+    }
+
+    override fun getFloat(key: String, defValue: Float): Float {
+        return data.getOrDefault(key, defValue) as Float
+    }
+
+    override fun getBoolean(key: String, defValue: Boolean): Boolean {
+        return data.getOrDefault(key, defValue) as Boolean
+    }
+
+    override fun contains(key: String): Boolean {
+        return key in data
+    }
+
+    override fun edit(): SharedPreferences.Editor {
+        return Editor()
+    }
+
+    override fun registerOnSharedPreferenceChangeListener(
+        listener: SharedPreferences.OnSharedPreferenceChangeListener
+    ) {
+        listeners.add(listener)
+    }
+
+    override fun unregisterOnSharedPreferenceChangeListener(
+        listener: SharedPreferences.OnSharedPreferenceChangeListener
+    ) {
+        listeners.remove(listener)
+    }
+
+    private inner class Editor : SharedPreferences.Editor {
+
+        private var clear = false
+        private val changes = mutableMapOf<String, Any>()
+        private val removals = mutableSetOf<String>()
+
+        override fun putString(key: String, value: String?): SharedPreferences.Editor {
+            if (value != null) {
+                changes[key] = value
+            } else {
+                removals.add(key)
+            }
+            return this
+        }
+
+        override fun putStringSet(
+            key: String,
+            values: MutableSet<String>?
+        ): SharedPreferences.Editor {
+            if (values != null) {
+                changes[key] = values
+            } else {
+                removals.add(key)
+            }
+            return this
+        }
+
+        override fun putInt(key: String, value: Int): SharedPreferences.Editor {
+            changes[key] = value
+            return this
+        }
+
+        override fun putLong(key: String, value: Long): SharedPreferences.Editor {
+            changes[key] = value
+            return this
+        }
+
+        override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
+            changes[key] = value
+            return this
+        }
+
+        override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
+            changes[key] = value
+            return this
+        }
+
+        override fun remove(key: String): SharedPreferences.Editor {
+            removals.add(key)
+            return this
+        }
+
+        override fun clear(): SharedPreferences.Editor {
+            clear = true
+            return this
+        }
+
+        override fun commit(): Boolean {
+            if (clear) {
+                data.clear()
+            }
+            removals.forEach { data.remove(it) }
+            data.putAll(changes)
+            val keys = removals + data.keys
+            if (clear || removals.isNotEmpty() || data.isNotEmpty()) {
+                listeners.forEach { listener ->
+                    if (clear) {
+                        listener.onSharedPreferenceChanged(this@FakeSharedPreferences, null)
+                    }
+                    keys.forEach {
+                        listener.onSharedPreferenceChanged(this@FakeSharedPreferences, it)
+                    }
+                }
+            }
+            return true
+        }
+
+        override fun apply() {
+            commit()
+        }
+    }
+}