Loading packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +37 −7 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import com.android.systemui.Dumpable; Loading Loading @@ -61,6 +62,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import javax.inject.Inject; Loading Loading @@ -91,6 +93,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); private int mCurrentUser; private final Optional<StatusBar> mStatusBarOptional; private Context mUserContext; @Inject public QSTileHost(Context context, Loading @@ -107,6 +110,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger) { mIconController = iconController; mContext = context; mUserContext = context; mTunerService = tunerService; mPluginManager = pluginManager; mDumpManager = dumpManager; Loading Loading @@ -207,6 +211,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D return mContext; } public Context getUserContext() { return mUserContext; } public TileServices getTileServices() { return mServices; Loading @@ -227,6 +234,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } final List<String> tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (currentUser != mCurrentUser) { mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0); } if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { Loading @@ -253,6 +263,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); } } else { // This means that the tile is a CustomTile AND the user is different, so let's // destroy it if (tile != null) { tile.destroy(); Log.d(TAG, "Destroying tile for wrong user: " + tileSpec); mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user"); } Log.d(TAG, "Creating tile: " + tileSpec); try { tile = createTile(tileSpec); Loading @@ -273,7 +290,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } } mCurrentUser = currentUser; List<String> currentSpecs = new ArrayList(mTileSpecs); List<String> currentSpecs = new ArrayList<>(mTileSpecs); mTileSpecs.clear(); mTileSpecs.addAll(tileSpecs); mTiles.clear(); Loading @@ -300,7 +317,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } public void addTile(String spec) { changeTileSpecs(tileSpecs-> tileSpecs.add(spec)); changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec)); } private void changeTileSpecs(Predicate<List<String>> changeFunction) { Loading @@ -314,10 +331,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } public void addTile(ComponentName tile) { String spec = CustomTile.toSpec(tile); if (!mTileSpecs.contains(spec)) { List<String> newSpecs = new ArrayList<>(mTileSpecs); newSpecs.add(0, CustomTile.toSpec(tile)); newSpecs.add(0, spec); changeTiles(mTileSpecs, newSpecs); } } public void removeTile(ComponentName tile) { List<String> newSpecs = new ArrayList<>(mTileSpecs); Loading Loading @@ -380,16 +400,26 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; Set<String> addedSpecs = new ArraySet<>(); for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; if (tile.equals("default")) { if (!addedDefault) { tiles.addAll(getDefaultSpecs(context)); List<String> defaultSpecs = getDefaultSpecs(context); for (String spec : defaultSpecs) { if (!addedSpecs.contains(spec)) { tiles.add(spec); addedSpecs.add(spec); } } addedDefault = true; } } else { if (!addedSpecs.contains(tile)) { tiles.add(tile); addedSpecs.add(tile); } } } return tiles; Loading packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +11 −7 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; Loading Loading @@ -72,15 +73,19 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; private final Context mUserContext; private boolean mListening; private boolean mIsTokenGranted; private boolean mIsShowingDialog; private CustomTile(QSTileHost host, String action) { private CustomTile(QSTileHost host, String action, Context userContext) { super(host); mWindowManager = WindowManagerGlobal.getWindowManagerService(); mComponent = ComponentName.unflattenFromString(action); mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); updateDefaultTileAndIcon(); mServiceManager = host.getTileServices().getTileWrapper(this); if (mServiceManager.isToggleableTile()) { Loading @@ -90,7 +95,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); mUser = ActivityManager.getCurrentUser(); } @Override Loading @@ -100,7 +104,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private void updateDefaultTileAndIcon() { try { PackageManager pm = mContext.getPackageManager(); PackageManager pm = mUserContext.getPackageManager(); int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; if (isSystemApp(pm)) { flags |= PackageManager.MATCH_DISABLED_COMPONENTS; Loading Loading @@ -318,11 +322,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.state = tileState; Drawable drawable; try { drawable = mTile.getIcon().loadDrawable(mContext); drawable = mTile.getIcon().loadDrawable(mUserContext); } catch (Exception e) { Log.w(TAG, "Invalid icon, forcing into unavailable state"); state.state = Tile.STATE_UNAVAILABLE; drawable = mDefaultIcon.loadDrawable(mContext); drawable = mDefaultIcon.loadDrawable(mUserContext); } final Drawable drawableF = drawable; Loading Loading @@ -388,7 +392,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return ComponentName.unflattenFromString(action); } public static CustomTile create(QSTileHost host, String spec) { public static CustomTile create(QSTileHost host, String spec, Context userContext) { if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) { throw new IllegalArgumentException("Bad custom tile spec: " + spec); } Loading @@ -396,6 +400,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener if (action.isEmpty()) { throw new IllegalArgumentException("Empty custom tile spec action"); } return new CustomTile(host, action); return new CustomTile(host, action, userContext); } } packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -279,6 +279,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { stopPackageListening(); } mChangeListener = null; } private void handleDeath() { Loading packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +3 −1 Original line number Diff line number Diff line Loading @@ -178,7 +178,9 @@ public class QSFactoryImpl implements QSFactory { } // Custom tiles if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); if (tileSpec.startsWith(CustomTile.PREFIX)) { return CustomTile.create(mHost, tileSpec, mHost.getUserContext()); } // Debug tiles. if (Build.IS_DEBUGGABLE) { Loading packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +71 −10 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Handler; Loading Loading @@ -76,7 +77,9 @@ import javax.inject.Provider; public class QSTileHostTest extends SysuiTestCase { private static String MOCK_STATE_STRING = "MockState"; private static final String CUSTOM_TILE_SPEC = "custom(TEST_PKG/.TEST_CLS)"; private static ComponentName CUSTOM_TILE = ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS"); private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE); @Mock private StatusBarIconController mIconController; Loading Loading @@ -114,23 +117,29 @@ public class QSTileHostTest extends SysuiTestCase { mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger); setUpTileFactory(); // Override this config so there are no unexpected tiles mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, ""); Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING, "", ActivityManager.getCurrentUser()); } private void setUpTileFactory() { when(mMockState.toString()).thenReturn(MOCK_STATE_STRING); // Only create this kind of tiles when(mDefaultFactory.createTile(anyString())).thenAnswer( invocation -> { String spec = invocation.getArgument(0); switch (spec) { case "spec1": if ("spec1".equals(spec)) { return new TestTile1(mQSTileHost); case "spec2": } else if ("spec2".equals(spec)) { return new TestTile2(mQSTileHost); case CUSTOM_TILE_SPEC: } else if (CUSTOM_TILE_SPEC.equals(spec)) { return mCustomTile; default: } else { return null; } }); Loading Loading @@ -222,6 +231,58 @@ public class QSTileHostTest extends SysuiTestCase { verify(mQSLogger).logTileAdded(CUSTOM_TILE_SPEC); } @Test public void testNoRepeatedSpecs_addTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2"); mQSTileHost.addTile("spec1"); assertEquals(2, mQSTileHost.mTileSpecs.size()); assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); } @Test public void testNoRepeatedSpecs_customTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC); mQSTileHost.addTile(CUSTOM_TILE); assertEquals(1, mQSTileHost.mTileSpecs.size()); assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0)); } @Test public void testLoadTileSpec_repeated() { List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); assertEquals(2, specs.size()); assertEquals("spec1", specs.get(0)); assertEquals("spec2", specs.get(1)); } @Test public void testLoadTileSpec_repeatedInDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); assertEquals(1, specs.size()); } @Test public void testLoadTileSpec_repeatedDefaultAndSetting() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); assertEquals(1, specs.size()); } private static class TestQSTileHost extends QSTileHost { TestQSTileHost(Context context, StatusBarIconController iconController, QSFactoryImpl defaultFactory, Handler mainHandler, Looper bgLooper, Loading Loading
packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +37 −7 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import com.android.systemui.Dumpable; Loading Loading @@ -61,6 +62,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import javax.inject.Inject; Loading Loading @@ -91,6 +93,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); private int mCurrentUser; private final Optional<StatusBar> mStatusBarOptional; private Context mUserContext; @Inject public QSTileHost(Context context, Loading @@ -107,6 +110,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D QSLogger qsLogger) { mIconController = iconController; mContext = context; mUserContext = context; mTunerService = tunerService; mPluginManager = pluginManager; mDumpManager = dumpManager; Loading Loading @@ -207,6 +211,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D return mContext; } public Context getUserContext() { return mUserContext; } public TileServices getTileServices() { return mServices; Loading @@ -227,6 +234,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } final List<String> tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (currentUser != mCurrentUser) { mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0); } if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { Loading @@ -253,6 +263,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); } } else { // This means that the tile is a CustomTile AND the user is different, so let's // destroy it if (tile != null) { tile.destroy(); Log.d(TAG, "Destroying tile for wrong user: " + tileSpec); mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user"); } Log.d(TAG, "Creating tile: " + tileSpec); try { tile = createTile(tileSpec); Loading @@ -273,7 +290,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } } mCurrentUser = currentUser; List<String> currentSpecs = new ArrayList(mTileSpecs); List<String> currentSpecs = new ArrayList<>(mTileSpecs); mTileSpecs.clear(); mTileSpecs.addAll(tileSpecs); mTiles.clear(); Loading @@ -300,7 +317,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } public void addTile(String spec) { changeTileSpecs(tileSpecs-> tileSpecs.add(spec)); changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec)); } private void changeTileSpecs(Predicate<List<String>> changeFunction) { Loading @@ -314,10 +331,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } public void addTile(ComponentName tile) { String spec = CustomTile.toSpec(tile); if (!mTileSpecs.contains(spec)) { List<String> newSpecs = new ArrayList<>(mTileSpecs); newSpecs.add(0, CustomTile.toSpec(tile)); newSpecs.add(0, spec); changeTiles(mTileSpecs, newSpecs); } } public void removeTile(ComponentName tile) { List<String> newSpecs = new ArrayList<>(mTileSpecs); Loading Loading @@ -380,16 +400,26 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; Set<String> addedSpecs = new ArraySet<>(); for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; if (tile.equals("default")) { if (!addedDefault) { tiles.addAll(getDefaultSpecs(context)); List<String> defaultSpecs = getDefaultSpecs(context); for (String spec : defaultSpecs) { if (!addedSpecs.contains(spec)) { tiles.add(spec); addedSpecs.add(spec); } } addedDefault = true; } } else { if (!addedSpecs.contains(tile)) { tiles.add(tile); addedSpecs.add(tile); } } } return tiles; Loading
packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +11 −7 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; Loading Loading @@ -72,15 +73,19 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private android.graphics.drawable.Icon mDefaultIcon; private CharSequence mDefaultLabel; private final Context mUserContext; private boolean mListening; private boolean mIsTokenGranted; private boolean mIsShowingDialog; private CustomTile(QSTileHost host, String action) { private CustomTile(QSTileHost host, String action, Context userContext) { super(host); mWindowManager = WindowManagerGlobal.getWindowManagerService(); mComponent = ComponentName.unflattenFromString(action); mTile = new Tile(); mUserContext = userContext; mUser = mUserContext.getUserId(); updateDefaultTileAndIcon(); mServiceManager = host.getTileServices().getTileWrapper(this); if (mServiceManager.isToggleableTile()) { Loading @@ -90,7 +95,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mService = mServiceManager.getTileService(); mServiceManager.setTileChangeListener(this); mUser = ActivityManager.getCurrentUser(); } @Override Loading @@ -100,7 +104,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private void updateDefaultTileAndIcon() { try { PackageManager pm = mContext.getPackageManager(); PackageManager pm = mUserContext.getPackageManager(); int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; if (isSystemApp(pm)) { flags |= PackageManager.MATCH_DISABLED_COMPONENTS; Loading Loading @@ -318,11 +322,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.state = tileState; Drawable drawable; try { drawable = mTile.getIcon().loadDrawable(mContext); drawable = mTile.getIcon().loadDrawable(mUserContext); } catch (Exception e) { Log.w(TAG, "Invalid icon, forcing into unavailable state"); state.state = Tile.STATE_UNAVAILABLE; drawable = mDefaultIcon.loadDrawable(mContext); drawable = mDefaultIcon.loadDrawable(mUserContext); } final Drawable drawableF = drawable; Loading Loading @@ -388,7 +392,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return ComponentName.unflattenFromString(action); } public static CustomTile create(QSTileHost host, String spec) { public static CustomTile create(QSTileHost host, String spec, Context userContext) { if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) { throw new IllegalArgumentException("Bad custom tile spec: " + spec); } Loading @@ -396,6 +400,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener if (action.isEmpty()) { throw new IllegalArgumentException("Empty custom tile spec action"); } return new CustomTile(host, action); return new CustomTile(host, action, userContext); } }
packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -279,6 +279,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { stopPackageListening(); } mChangeListener = null; } private void handleDeath() { Loading
packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +3 −1 Original line number Diff line number Diff line Loading @@ -178,7 +178,9 @@ public class QSFactoryImpl implements QSFactory { } // Custom tiles if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); if (tileSpec.startsWith(CustomTile.PREFIX)) { return CustomTile.create(mHost, tileSpec, mHost.getUserContext()); } // Debug tiles. if (Build.IS_DEBUGGABLE) { Loading
packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +71 −10 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Handler; Loading Loading @@ -76,7 +77,9 @@ import javax.inject.Provider; public class QSTileHostTest extends SysuiTestCase { private static String MOCK_STATE_STRING = "MockState"; private static final String CUSTOM_TILE_SPEC = "custom(TEST_PKG/.TEST_CLS)"; private static ComponentName CUSTOM_TILE = ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS"); private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE); @Mock private StatusBarIconController mIconController; Loading Loading @@ -114,23 +117,29 @@ public class QSTileHostTest extends SysuiTestCase { mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger); setUpTileFactory(); // Override this config so there are no unexpected tiles mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, ""); Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING, "", ActivityManager.getCurrentUser()); } private void setUpTileFactory() { when(mMockState.toString()).thenReturn(MOCK_STATE_STRING); // Only create this kind of tiles when(mDefaultFactory.createTile(anyString())).thenAnswer( invocation -> { String spec = invocation.getArgument(0); switch (spec) { case "spec1": if ("spec1".equals(spec)) { return new TestTile1(mQSTileHost); case "spec2": } else if ("spec2".equals(spec)) { return new TestTile2(mQSTileHost); case CUSTOM_TILE_SPEC: } else if (CUSTOM_TILE_SPEC.equals(spec)) { return mCustomTile; default: } else { return null; } }); Loading Loading @@ -222,6 +231,58 @@ public class QSTileHostTest extends SysuiTestCase { verify(mQSLogger).logTileAdded(CUSTOM_TILE_SPEC); } @Test public void testNoRepeatedSpecs_addTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2"); mQSTileHost.addTile("spec1"); assertEquals(2, mQSTileHost.mTileSpecs.size()); assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); } @Test public void testNoRepeatedSpecs_customTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC); mQSTileHost.addTile(CUSTOM_TILE); assertEquals(1, mQSTileHost.mTileSpecs.size()); assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0)); } @Test public void testLoadTileSpec_repeated() { List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); assertEquals(2, specs.size()); assertEquals("spec1", specs.get(0)); assertEquals("spec2", specs.get(1)); } @Test public void testLoadTileSpec_repeatedInDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); assertEquals(1, specs.size()); } @Test public void testLoadTileSpec_repeatedDefaultAndSetting() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1"); List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); assertEquals(1, specs.size()); } private static class TestQSTileHost extends QSTileHost { TestQSTileHost(Context context, StatusBarIconController iconController, QSFactoryImpl defaultFactory, Handler mainHandler, Looper bgLooper, Loading