Merge "Use the correct user context in CustomTile" into rvc-dev

This commit is contained in:
Fabian Kozynski
2020-02-28 19:05:17 +00:00
committed by Android (Google) Code Review
6 changed files with 140 additions and 28 deletions

View File

@@ -28,6 +28,7 @@ import android.provider.Settings;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.service.quicksettings.Tile; import android.service.quicksettings.Tile;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
@@ -61,6 +62,7 @@ import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.inject.Inject; import javax.inject.Inject;
@@ -91,6 +93,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
private int mCurrentUser; private int mCurrentUser;
private final Optional<StatusBar> mStatusBarOptional; private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
@Inject @Inject
public QSTileHost(Context context, public QSTileHost(Context context,
@@ -107,6 +110,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
QSLogger qsLogger) { QSLogger qsLogger) {
mIconController = iconController; mIconController = iconController;
mContext = context; mContext = context;
mUserContext = context;
mTunerService = tunerService; mTunerService = tunerService;
mPluginManager = pluginManager; mPluginManager = pluginManager;
mDumpManager = dumpManager; mDumpManager = dumpManager;
@@ -207,6 +211,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
return mContext; return mContext;
} }
public Context getUserContext() {
return mUserContext;
}
public TileServices getTileServices() { public TileServices getTileServices() {
return mServices; return mServices;
@@ -227,6 +234,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
} }
final List<String> tileSpecs = loadTileSpecs(mContext, newValue); final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
int currentUser = ActivityManager.getCurrentUser(); int currentUser = ActivityManager.getCurrentUser();
if (currentUser != mCurrentUser) {
mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
}
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> { tile -> {
@@ -253,6 +263,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
} }
} else { } 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); Log.d(TAG, "Creating tile: " + tileSpec);
try { try {
tile = createTile(tileSpec); tile = createTile(tileSpec);
@@ -273,7 +290,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
} }
} }
mCurrentUser = currentUser; mCurrentUser = currentUser;
List<String> currentSpecs = new ArrayList(mTileSpecs); List<String> currentSpecs = new ArrayList<>(mTileSpecs);
mTileSpecs.clear(); mTileSpecs.clear();
mTileSpecs.addAll(tileSpecs); mTileSpecs.addAll(tileSpecs);
mTiles.clear(); mTiles.clear();
@@ -300,7 +317,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
} }
public void addTile(String spec) { 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) { private void changeTileSpecs(Predicate<List<String>> changeFunction) {
@@ -314,10 +331,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
} }
public void addTile(ComponentName tile) { public void addTile(ComponentName tile) {
String spec = CustomTile.toSpec(tile);
if (!mTileSpecs.contains(spec)) {
List<String> newSpecs = new ArrayList<>(mTileSpecs); List<String> newSpecs = new ArrayList<>(mTileSpecs);
newSpecs.add(0, CustomTile.toSpec(tile)); newSpecs.add(0, spec);
changeTiles(mTileSpecs, newSpecs); changeTiles(mTileSpecs, newSpecs);
} }
}
public void removeTile(ComponentName tile) { public void removeTile(ComponentName tile) {
List<String> newSpecs = new ArrayList<>(mTileSpecs); List<String> newSpecs = new ArrayList<>(mTileSpecs);
@@ -380,16 +400,26 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
} }
final ArrayList<String> tiles = new ArrayList<String>(); final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false; boolean addedDefault = false;
Set<String> addedSpecs = new ArraySet<>();
for (String tile : tileList.split(",")) { for (String tile : tileList.split(",")) {
tile = tile.trim(); tile = tile.trim();
if (tile.isEmpty()) continue; if (tile.isEmpty()) continue;
if (tile.equals("default")) { if (tile.equals("default")) {
if (!addedDefault) { 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; addedDefault = true;
} }
} else { } else {
if (!addedSpecs.contains(tile)) {
tiles.add(tile); tiles.add(tile);
addedSpecs.add(tile);
}
} }
} }
return tiles; return tiles;

View File

@@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
@@ -72,15 +73,19 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
private android.graphics.drawable.Icon mDefaultIcon; private android.graphics.drawable.Icon mDefaultIcon;
private CharSequence mDefaultLabel; private CharSequence mDefaultLabel;
private final Context mUserContext;
private boolean mListening; private boolean mListening;
private boolean mIsTokenGranted; private boolean mIsTokenGranted;
private boolean mIsShowingDialog; private boolean mIsShowingDialog;
private CustomTile(QSTileHost host, String action) { private CustomTile(QSTileHost host, String action, Context userContext) {
super(host); super(host);
mWindowManager = WindowManagerGlobal.getWindowManagerService(); mWindowManager = WindowManagerGlobal.getWindowManagerService();
mComponent = ComponentName.unflattenFromString(action); mComponent = ComponentName.unflattenFromString(action);
mTile = new Tile(); mTile = new Tile();
mUserContext = userContext;
mUser = mUserContext.getUserId();
updateDefaultTileAndIcon(); updateDefaultTileAndIcon();
mServiceManager = host.getTileServices().getTileWrapper(this); mServiceManager = host.getTileServices().getTileWrapper(this);
if (mServiceManager.isToggleableTile()) { if (mServiceManager.isToggleableTile()) {
@@ -90,7 +95,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
mService = mServiceManager.getTileService(); mService = mServiceManager.getTileService();
mServiceManager.setTileChangeListener(this); mServiceManager.setTileChangeListener(this);
mUser = ActivityManager.getCurrentUser();
} }
@Override @Override
@@ -100,7 +104,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
private void updateDefaultTileAndIcon() { private void updateDefaultTileAndIcon() {
try { try {
PackageManager pm = mContext.getPackageManager(); PackageManager pm = mUserContext.getPackageManager();
int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE;
if (isSystemApp(pm)) { if (isSystemApp(pm)) {
flags |= PackageManager.MATCH_DISABLED_COMPONENTS; flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
@@ -318,11 +322,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
state.state = tileState; state.state = tileState;
Drawable drawable; Drawable drawable;
try { try {
drawable = mTile.getIcon().loadDrawable(mContext); drawable = mTile.getIcon().loadDrawable(mUserContext);
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state"); Log.w(TAG, "Invalid icon, forcing into unavailable state");
state.state = Tile.STATE_UNAVAILABLE; state.state = Tile.STATE_UNAVAILABLE;
drawable = mDefaultIcon.loadDrawable(mContext); drawable = mDefaultIcon.loadDrawable(mUserContext);
} }
final Drawable drawableF = drawable; final Drawable drawableF = drawable;
@@ -388,7 +392,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
return ComponentName.unflattenFromString(action); 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(")")) { if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
throw new IllegalArgumentException("Bad custom tile spec: " + spec); throw new IllegalArgumentException("Bad custom tile spec: " + spec);
} }
@@ -396,6 +400,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
if (action.isEmpty()) { if (action.isEmpty()) {
throw new IllegalArgumentException("Empty custom tile spec action"); throw new IllegalArgumentException("Empty custom tile spec action");
} }
return new CustomTile(host, action); return new CustomTile(host, action, userContext);
} }
} }

View File

@@ -279,6 +279,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) { if (mPackageReceiverRegistered.get() || mUserReceiverRegistered.get()) {
stopPackageListening(); stopPackageListening();
} }
mChangeListener = null;
} }
private void handleDeath() { private void handleDeath() {

View File

@@ -178,7 +178,9 @@ public class QSFactoryImpl implements QSFactory {
} }
// Custom tiles // 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. // Debug tiles.
if (Build.IS_DEBUGGABLE) { if (Build.IS_DEBUGGABLE) {

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Handler; import android.os.Handler;
@@ -76,7 +77,9 @@ import javax.inject.Provider;
public class QSTileHostTest extends SysuiTestCase { public class QSTileHostTest extends SysuiTestCase {
private static String MOCK_STATE_STRING = "MockState"; 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 @Mock
private StatusBarIconController mIconController; private StatusBarIconController mIconController;
@@ -114,23 +117,29 @@ public class QSTileHostTest extends SysuiTestCase {
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger); mBroadcastDispatcher, mStatusBar, mQSLogger);
setUpTileFactory(); 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, Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
"", ActivityManager.getCurrentUser()); "", ActivityManager.getCurrentUser());
} }
private void setUpTileFactory() { private void setUpTileFactory() {
when(mMockState.toString()).thenReturn(MOCK_STATE_STRING); when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
when(mDefaultFactory.createTile(anyString())).thenAnswer( when(mDefaultFactory.createTile(anyString())).thenAnswer(
invocation -> { invocation -> {
String spec = invocation.getArgument(0); String spec = invocation.getArgument(0);
switch (spec) { if ("spec1".equals(spec)) {
case "spec1":
return new TestTile1(mQSTileHost); return new TestTile1(mQSTileHost);
case "spec2": } else if ("spec2".equals(spec)) {
return new TestTile2(mQSTileHost); return new TestTile2(mQSTileHost);
case CUSTOM_TILE_SPEC: } else if (CUSTOM_TILE_SPEC.equals(spec)) {
return mCustomTile; return mCustomTile;
default: } else {
return null; return null;
} }
}); });
@@ -222,6 +231,58 @@ public class QSTileHostTest extends SysuiTestCase {
verify(mQSLogger).logTileAdded(CUSTOM_TILE_SPEC); 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 { private static class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController, TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactoryImpl defaultFactory, Handler mainHandler, Looper bgLooper, QSFactoryImpl defaultFactory, Handler mainHandler, Looper bgLooper,

View File

@@ -33,6 +33,7 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.QSTileHost import com.android.systemui.qs.QSTileHost
import junit.framework.Assert.assertFalse import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue import junit.framework.Assert.assertTrue
import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -84,13 +85,26 @@ class CustomTileTest : SysuiTestCase() {
.thenReturn(mServiceInfo) .thenReturn(mServiceInfo)
mServiceInfo.applicationInfo = mApplicationInfo mServiceInfo.applicationInfo = mApplicationInfo
customTile = CustomTile.create(mTileHost, TILE_SPEC) customTile = CustomTile.create(mTileHost, TILE_SPEC, mContext)
}
@Test
fun testCorrectUser() {
assertEquals(0, customTile.user)
val userContext = mock(Context::class.java)
`when`(userContext.packageManager).thenReturn(mPackageManager)
`when`(userContext.userId).thenReturn(10)
val tile = CustomTile.create(mTileHost, TILE_SPEC, userContext)
assertEquals(10, tile.user)
} }
@Test @Test
fun testToggleableTileHasBooleanState() { fun testToggleableTileHasBooleanState() {
`when`(mTileServiceManager.isToggleableTile).thenReturn(true) `when`(mTileServiceManager.isToggleableTile).thenReturn(true)
customTile = CustomTile.create(mTileHost, TILE_SPEC) customTile = CustomTile.create(mTileHost, TILE_SPEC, mContext)
assertTrue(customTile.state is QSTile.BooleanState) assertTrue(customTile.state is QSTile.BooleanState)
assertTrue(customTile.newTileState() is QSTile.BooleanState) assertTrue(customTile.newTileState() is QSTile.BooleanState)
@@ -105,7 +119,7 @@ class CustomTileTest : SysuiTestCase() {
@Test @Test
fun testValueUpdatedInBooleanTile() { fun testValueUpdatedInBooleanTile() {
`when`(mTileServiceManager.isToggleableTile).thenReturn(true) `when`(mTileServiceManager.isToggleableTile).thenReturn(true)
customTile = CustomTile.create(mTileHost, TILE_SPEC) customTile = CustomTile.create(mTileHost, TILE_SPEC, mContext)
customTile.qsTile.icon = mock(Icon::class.java) customTile.qsTile.icon = mock(Icon::class.java)
`when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java)))
.thenReturn(mock(Drawable::class.java)) .thenReturn(mock(Drawable::class.java))