Merge changes I966312e0,Ie443f626 into tm-qpr-dev

* changes:
  Properly store CustomTile added/removed
  Add a FakeSharedPreferences for testing
This commit is contained in:
Fabian Kozynski
2022-08-10 13:36:22 +00:00
committed by Android (Google) Code Review
8 changed files with 693 additions and 44 deletions

View File

@@ -29,6 +29,7 @@ import android.util.Log;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
@@ -47,6 +48,7 @@ import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -88,6 +90,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
public static final int POSITION_AT_END = -1; public static final int POSITION_AT_END = -1;
public static final String TILES_SETTING = Secure.QS_TILES; 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 Context mContext;
private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>(); private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>(); protected final ArrayList<String> mTileSpecs = new ArrayList<>();
@@ -99,6 +105,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final InstanceIdSequence mInstanceIdSequence; private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister; private final CustomTileStatePersister mCustomTileStatePersister;
private final Executor mMainExecutor; private final Executor mMainExecutor;
private final UserFileManager mUserFileManager;
private final List<Callback> mCallbacks = new ArrayList<>(); private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable @Nullable
@@ -135,7 +142,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
SecureSettings secureSettings, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister, CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager
) { ) {
mIconController = iconController; mIconController = iconController;
mContext = context; mContext = context;
@@ -148,6 +156,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mMainExecutor = mainExecutor; mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this); mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
mUserFileManager = userFileManager;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID); mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mCentralSurfacesOptional = centralSurfacesOptional; mCentralSurfacesOptional = centralSurfacesOptional;
@@ -392,6 +401,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
*/ */
@Override @Override
public void removeTile(String spec) { 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))); mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
} }
@@ -515,7 +529,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
lifecycleManager.onStopListening(); lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved(); lifecycleManager.onTileRemoved();
mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
TileLifecycleManager.setTileAdded(mContext, component, false); setTileAdded(component, mCurrentUser, false);
lifecycleManager.flushMessagesAndUnbind(); lifecycleManager.flushMessagesAndUnbind();
} }
} }
@@ -552,6 +566,36 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec()); 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) { protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources(); final Resources res = context.getResources();

View File

@@ -127,6 +127,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements
TileLifecycleManager create(Intent intent, UserHandle userHandle); TileLifecycleManager create(Intent intent, UserHandle userHandle);
} }
public int getUserId() {
return mUser.getIdentifier();
}
public ComponentName getComponent() { public ComponentName getComponent() {
return mIntent.getComponent(); return mIntent.getComponent();
} }
@@ -507,13 +511,4 @@ public class TileLifecycleManager extends BroadcastReceiver implements
public interface TileChangeListener { public interface TileChangeListener {
void onTileChanged(ComponentName tile); 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();
}
} }

View File

@@ -109,9 +109,9 @@ public class TileServiceManager {
void startLifecycleManagerAndAddTile() { void startLifecycleManagerAndAddTile() {
mStarted = true; mStarted = true;
ComponentName component = mStateManager.getComponent(); ComponentName component = mStateManager.getComponent();
Context context = mServices.getContext(); final int userId = mStateManager.getUserId();
if (!TileLifecycleManager.isTileAdded(context, component)) { if (!mServices.getHost().isTileAdded(component, userId)) {
TileLifecycleManager.setTileAdded(context, component, true); mServices.getHost().setTileAdded(component, userId, true);
mStateManager.onTileAdded(); mStateManager.onTileAdded();
mStateManager.flushMessagesAndUnbind(); mStateManager.flushMessagesAndUnbind();
} }

View File

@@ -22,6 +22,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -32,11 +33,13 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.util.SparseArray;
import android.view.View; import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -60,12 +63,14 @@ import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.FakeSharedPreferences;
import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SecureSettings;
@@ -76,6 +81,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@@ -130,6 +136,10 @@ public class QSTileHostTest extends SysuiTestCase {
private TileLifecycleManager.Factory mTileLifecycleManagerFactory; private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock @Mock
private TileLifecycleManager mTileLifecycleManager; private TileLifecycleManager mTileLifecycleManager;
@Mock
private UserFileManager mUserFileManager;
private SparseArray<SharedPreferences> mSharedPreferencesByUser;
private FakeExecutor mMainExecutor; private FakeExecutor mMainExecutor;
@@ -140,17 +150,29 @@ public class QSTileHostTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mMainExecutor = new FakeExecutor(new FakeSystemClock()); mMainExecutor = new FakeExecutor(new FakeSystemClock());
mSharedPreferencesByUser = new SparseArray<>();
when(mTileServiceRequestControllerBuilder.create(any())) when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController); .thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager); .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(); mSecureSettings = new FakeSettings();
saveSetting(""); saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor, mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces, mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory); mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
mUserFileManager);
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) { mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override @Override
@@ -528,6 +550,118 @@ public class QSTileHostTest extends SysuiTestCase {
assertEquals("spec1", getSetting()); 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 { private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController, TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Executor mainExecutor, QSFactory defaultFactory, Executor mainExecutor,
@@ -537,11 +671,13 @@ public class QSTileHostTest extends SysuiTestCase {
UserTracker userTracker, SecureSettings secureSettings, UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister, CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory) { TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager) {
super(context, iconController, defaultFactory, mainExecutor, pluginManager, super(context, iconController, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger, tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
uiEventLogger, userTracker, secureSettings, customTileStatePersister, uiEventLogger, userTracker, secureSettings, customTileStatePersister,
tileServiceRequestControllerBuilder, tileLifecycleManagerFactory); tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
userFileManager);
} }
@Override @Override

View File

@@ -19,6 +19,14 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; 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.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -31,6 +39,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import org.junit.After; import org.junit.After;
@@ -38,37 +47,45 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TileServiceManagerTest extends SysuiTestCase { public class TileServiceManagerTest extends SysuiTestCase {
@Mock
private TileServices mTileServices; private TileServices mTileServices;
@Mock
private TileLifecycleManager mTileLifecycle; private TileLifecycleManager mTileLifecycle;
@Mock
private UserTracker mUserTracker;
@Mock
private QSTileHost mQSTileHost;
@Mock
private Context mMockContext;
private HandlerThread mThread; private HandlerThread mThread;
private Handler mHandler; private Handler mHandler;
private TileServiceManager mTileServiceManager; private TileServiceManager mTileServiceManager;
private UserTracker mUserTracker; private ComponentName mComponentName;
private Context mMockContext;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("TestThread"); mThread = new HandlerThread("TestThread");
mThread.start(); mThread.start();
mHandler = Handler.createAsync(mThread.getLooper()); mHandler = Handler.createAsync(mThread.getLooper());
mTileServices = Mockito.mock(TileServices.class); when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
mUserTracker = Mockito.mock(UserTracker.class); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
mMockContext = Mockito.mock(Context.class); when(mTileServices.getContext()).thenReturn(mMockContext);
Mockito.when(mTileServices.getContext()).thenReturn(mMockContext); when(mTileServices.getHost()).thenReturn(mQSTileHost);
mTileLifecycle = Mockito.mock(TileLifecycleManager.class); when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false); when(mTileLifecycle.isActiveTile()).thenReturn(false);
ComponentName componentName = new ComponentName(mContext,
TileServiceManagerTest.class); mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName); when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker, mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
mTileLifecycle); mTileLifecycle);
} }
@@ -79,18 +96,45 @@ public class TileServiceManagerTest extends SysuiTestCase {
mTileServiceManager.handleDestroy(); mTileServiceManager.handleDestroy();
} }
@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 @Test
public void testUninstallReceiverExported() { public void testUninstallReceiverExported() {
mTileServiceManager.startLifecycleManagerAndAddTile();
ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor<IntentFilter> intentFilterCaptor =
ArgumentCaptor.forClass(IntentFilter.class); ArgumentCaptor.forClass(IntentFilter.class);
Mockito.verify(mMockContext).registerReceiverAsUser( verify(mMockContext).registerReceiverAsUser(
Mockito.any(), any(),
Mockito.any(), any(),
intentFilterCaptor.capture(), intentFilterCaptor.capture(),
Mockito.any(), any(),
Mockito.any(), any(),
Mockito.eq(Context.RECEIVER_EXPORTED) eq(Context.RECEIVER_EXPORTED)
); );
IntentFilter filter = intentFilterCaptor.getValue(); IntentFilter filter = intentFilterCaptor.getValue();
assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)); assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
@@ -99,38 +143,41 @@ public class TileServiceManagerTest extends SysuiTestCase {
@Test @Test
public void testSetBindRequested() { public void testSetBindRequested() {
mTileServiceManager.startLifecycleManagerAndAddTile();
// Request binding. // Request binding.
mTileServiceManager.setBindRequested(true); mTileServiceManager.setBindRequested(true);
mTileServiceManager.setLastUpdate(0); mTileServiceManager.setLastUpdate(0);
mTileServiceManager.calculateBindPriority(5); mTileServiceManager.calculateBindPriority(5);
Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance(); verify(mTileServices, times(2)).recalculateBindAllowance();
assertEquals(5, mTileServiceManager.getBindPriority()); assertEquals(5, mTileServiceManager.getBindPriority());
// Verify same state doesn't trigger recalculating for no reason. // Verify same state doesn't trigger recalculating for no reason.
mTileServiceManager.setBindRequested(true); mTileServiceManager.setBindRequested(true);
Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance(); verify(mTileServices, times(2)).recalculateBindAllowance();
mTileServiceManager.setBindRequested(false); mTileServiceManager.setBindRequested(false);
mTileServiceManager.calculateBindPriority(5); mTileServiceManager.calculateBindPriority(5);
Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance(); verify(mTileServices, times(3)).recalculateBindAllowance();
assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority()); assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());
} }
@Test @Test
public void testPendingClickPriority() { public void testPendingClickPriority() {
Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true); mTileServiceManager.startLifecycleManagerAndAddTile();
when(mTileLifecycle.hasPendingClick()).thenReturn(true);
mTileServiceManager.calculateBindPriority(0); mTileServiceManager.calculateBindPriority(0);
assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority()); assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());
} }
@Test @Test
public void testBind() { public void testBind() {
mTileServiceManager.startLifecycleManagerAndAddTile();
// Trigger binding requested and allowed. // Trigger binding requested and allowed.
mTileServiceManager.setBindRequested(true); mTileServiceManager.setBindRequested(true);
mTileServiceManager.setBindAllowed(true); mTileServiceManager.setBindAllowed(true);
ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class); 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()); assertTrue((boolean) captor.getValue());
mTileServiceManager.setBindRequested(false); mTileServiceManager.setBindRequested(false);
@@ -141,7 +188,7 @@ public class TileServiceManagerTest extends SysuiTestCase {
mTileServiceManager.setBindAllowed(false); mTileServiceManager.setBindAllowed(false);
captor = ArgumentCaptor.forClass(Boolean.class); 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()); assertFalse((boolean) captor.getValue());
} }
} }

View File

@@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue;
@@ -118,6 +119,8 @@ public class TileServicesTest extends SysuiTestCase {
private TileLifecycleManager.Factory mTileLifecycleManagerFactory; private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock @Mock
private TileLifecycleManager mTileLifecycleManager; private TileLifecycleManager mTileLifecycleManager;
@Mock
private UserFileManager mUserFileManager;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -149,7 +152,8 @@ public class TileServicesTest extends SysuiTestCase {
mSecureSettings, mSecureSettings,
mock(CustomTileStatePersister.class), mock(CustomTileStatePersister.class),
mTileServiceRequestControllerBuilder, mTileServiceRequestControllerBuilder,
mTileLifecycleManagerFactory); mTileLifecycleManagerFactory,
mUserFileManager);
mTileService = new TestTileServices(host, provider, mBroadcastDispatcher, mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
mUserTracker, mKeyguardStateController, mCommandQueue); mUserTracker, mKeyguardStateController, mCommandQueue);
} }

View File

@@ -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)
}
}
}

View File

@@ -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()
}
}
}