Add multi-user support for AutoTileManager

Before this CL, AutoTileManager (and AutoAddTracker) would only work
"corectly" for user 0, as all the settings (read and write) were done in
user 0. This meant that tiles that got auto added as user 0 would not be
auto-added for other users. Similarly, tiles that got auto added as a
secondary user would not be auto added as user 0.

This CL makes sure that everything is done with respect to the current
user (as reported by QSTileHost).

Checks if the color display supports inversion in tests.

Test: manual
Test: atest AutoTileManagerTest AutoAddTrackerTest
Fixes: 154607890

Merged-In: I518334cca0a0960e0b3b74b43ac9fce4610c7bd0
Change-Id: Icc1406ebd2bf94da3a427f3b13f11b325e25b491
This commit is contained in:
Fabian Kozynski
2020-04-22 09:27:25 -04:00
parent c744ad246e
commit 61ebaf5c46
9 changed files with 302 additions and 48 deletions

View File

@@ -20,7 +20,7 @@ import android.content.ComponentName
import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.service.controls.actions.ControlAction
import com.android.systemui.controls.UserAwareController
import com.android.systemui.util.UserAwareController
import java.util.function.Consumer
/**

View File

@@ -21,7 +21,7 @@ import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.service.controls.actions.ControlAction
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.UserAwareController
import com.android.systemui.util.UserAwareController
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.ui.ControlsUiController
import java.util.function.Consumer

View File

@@ -18,7 +18,7 @@ package com.android.systemui.controls.management
import android.content.ComponentName
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.UserAwareController
import com.android.systemui.util.UserAwareController
import com.android.systemui.statusbar.policy.CallbackController
/**
@@ -26,7 +26,7 @@ import com.android.systemui.statusbar.policy.CallbackController
*/
interface ControlsListingController :
CallbackController<ControlsListingController.ControlsListingCallback>,
UserAwareController {
UserAwareController {
/**
* @return the current list of services that satisfies the [ServiceListing].

View File

@@ -23,6 +23,7 @@ import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -30,6 +31,7 @@ import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
import com.android.systemui.util.UserAwareController;
import java.util.Arrays;
import java.util.Collection;
@@ -37,7 +39,7 @@ import java.util.Collections;
import javax.inject.Inject;
public class AutoAddTracker {
public class AutoAddTracker implements UserAwareController {
private static final String[][] CONVERT_PREFS = {
{Key.QS_HOTSPOT_ADDED, HOTSPOT},
@@ -49,20 +51,39 @@ public class AutoAddTracker {
private final ArraySet<String> mAutoAdded;
private final Context mContext;
private int mUserId;
@Inject
public AutoAddTracker(Context context) {
public AutoAddTracker(Context context, int userId) {
mContext = context;
mUserId = userId;
mAutoAdded = new ArraySet<>(getAdded());
// TODO: remove migration code and shared preferences keys after P release
for (String[] convertPref : CONVERT_PREFS) {
if (Prefs.getBoolean(context, convertPref[0], false)) {
setTileAdded(convertPref[1]);
Prefs.remove(context, convertPref[0]);
if (mUserId == UserHandle.USER_SYSTEM) {
for (String[] convertPref : CONVERT_PREFS) {
if (Prefs.getBoolean(context, convertPref[0], false)) {
setTileAdded(convertPref[1]);
Prefs.remove(context, convertPref[0]);
}
}
}
mContext.getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver);
Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
UserHandle.USER_ALL);
}
@Override
public void changeUser(UserHandle newUser) {
if (newUser.getIdentifier() == mUserId) {
return;
}
mUserId = newUser.getIdentifier();
mAutoAdded.clear();
mAutoAdded.addAll(getAdded());
}
@Override
public int getCurrentUserId() {
return mUserId;
}
public boolean isAdded(String tile) {
@@ -86,12 +107,13 @@ public class AutoAddTracker {
}
private void saveTiles() {
Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
TextUtils.join(",", mAutoAdded));
Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
TextUtils.join(",", mAutoAdded), mUserId);
}
private Collection<String> getAdded() {
String current = Secure.getString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES);
String current = Secure.getStringForUser(mContext.getContentResolver(),
Secure.QS_AUTO_ADDED_TILES, mUserId);
if (current == null) {
return Collections.emptyList();
}
@@ -102,7 +124,27 @@ public class AutoAddTracker {
protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
mAutoAdded.clear();
mAutoAdded.addAll(getAdded());
}
};
public static class Builder {
private final Context mContext;
private int mUserId;
@Inject
public Builder(Context context) {
mContext = context;
}
public Builder setUserId(int userId) {
mUserId = userId;
return this;
}
public AutoAddTracker build() {
return new AutoAddTracker(mContext, mUserId);
}
}
}

View File

@@ -255,6 +255,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
int currentUser = ActivityManager.getCurrentUser();
if (currentUser != mCurrentUser) {
mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
if (mAutoTiles != null) {
mAutoTiles.changeUser(UserHandle.of(currentUser));
}
}
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(

View File

@@ -19,6 +19,7 @@ import android.content.res.Resources;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
import com.android.systemui.util.UserAwareController;
import java.util.ArrayList;
import java.util.Objects;
@@ -43,7 +45,7 @@ import javax.inject.Inject;
/**
* Manages which tiles should be automatically added to QS.
*/
public class AutoTileManager {
public class AutoTileManager implements UserAwareController {
private static final String TAG = "AutoTileManager";
public static final String HOTSPOT = "hotspot";
@@ -52,7 +54,9 @@ public class AutoTileManager {
public static final String WORK = "work";
public static final String NIGHT = "night";
public static final String CAST = "cast";
public static final String SETTING_SEPARATOR = ":";
static final String SETTING_SEPARATOR = ":";
private UserHandle mCurrentUser;
private final Context mContext;
private final QSTileHost mHost;
@@ -66,43 +70,56 @@ public class AutoTileManager {
private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
@Inject
public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
QSTileHost host,
@Background Handler handler,
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
NightDisplayListener nightDisplayListener,
CastController castController) {
mAutoTracker = autoAddTracker;
mContext = context;
mHost = host;
mCurrentUser = mHost.getUserContext().getUser();
mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
mHandler = handler;
mHotspotController = hotspotController;
mDataSaverController = dataSaverController;
mManagedProfileController = managedProfileController;
mNightDisplayListener = nightDisplayListener;
mCastController = castController;
populateSettingsList();
startControllersAndSettingsListeners();
}
protected void startControllersAndSettingsListeners() {
if (!mAutoTracker.isAdded(HOTSPOT)) {
hotspotController.addCallback(mHotspotCallback);
mHotspotController.addCallback(mHotspotCallback);
}
if (!mAutoTracker.isAdded(SAVER)) {
dataSaverController.addCallback(mDataSaverListener);
mDataSaverController.addCallback(mDataSaverListener);
}
if (!mAutoTracker.isAdded(WORK)) {
managedProfileController.addCallback(mProfileCallback);
mManagedProfileController.addCallback(mProfileCallback);
}
if (!mAutoTracker.isAdded(NIGHT)
&& ColorDisplayManager.isNightDisplayAvailable(mContext)) {
nightDisplayListener.setCallback(mNightDisplayCallback);
mNightDisplayListener.setCallback(mNightDisplayCallback);
}
if (!mAutoTracker.isAdded(CAST)) {
castController.addCallback(mCastCallback);
mCastController.addCallback(mCastCallback);
}
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
mAutoAddSettingList.get(i).setListening(true);
}
}
populateSettingsList();
}
public void destroy() {
mAutoTracker.destroy();
protected void stopListening() {
mHotspotController.removeCallback(mHotspotCallback);
mDataSaverController.removeCallback(mDataSaverListener);
mManagedProfileController.removeCallback(mProfileCallback);
@@ -116,6 +133,11 @@ public class AutoTileManager {
}
}
public void destroy() {
stopListening();
mAutoTracker.destroy();
}
/**
* Populates a list with the pairs setting:spec in the config resource.
* <p>
@@ -137,17 +159,39 @@ public class AutoTileManager {
if (split.length == 2) {
String setting = split[0];
String spec = split[1];
if (!mAutoTracker.isAdded(spec)) {
AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
mAutoAddSettingList.add(s);
s.setListening(true);
}
// Populate all the settings. As they may not have been added in other users
AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
mAutoAddSettingList.add(s);
} else {
Log.w(TAG, "Malformed item in array: " + tile);
}
}
}
@Override
public void changeUser(UserHandle newUser) {
if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
mHandler.post(() -> changeUser(newUser));
return;
}
if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
return;
}
stopListening();
mCurrentUser = newUser;
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
}
mAutoTracker.changeUser(newUser);
startControllersAndSettingsListeners();
}
@Override
public int getCurrentUserId() {
return mCurrentUser.getIdentifier();
}
public void unmarkTileAsAutoAdded(String tabSpec) {
mAutoTracker.setTileRemoved(tabSpec);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.controls
package com.android.systemui.util
import android.os.UserHandle
@@ -23,6 +23,8 @@ import android.os.UserHandle
* changes.
*/
interface UserAwareController {
@JvmDefault
fun changeUser(newUser: UserHandle) {}
val currentUserId: Int
}

View File

@@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -40,6 +41,8 @@ import org.junit.runner.RunWith;
@SmallTest
public class AutoAddTrackerTest extends SysuiTestCase {
private static final int USER = 0;
private AutoAddTracker mAutoTracker;
@Before
@@ -51,7 +54,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
public void testMigration() {
Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
mAutoTracker = new AutoAddTracker(mContext);
mAutoTracker = new AutoAddTracker(mContext, USER);
assertTrue(mAutoTracker.isAdded(SAVER));
assertTrue(mAutoTracker.isAdded(WORK));
@@ -68,7 +71,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testChangeFromBackup() {
mAutoTracker = new AutoAddTracker(mContext);
mAutoTracker = new AutoAddTracker(mContext, USER);
assertFalse(mAutoTracker.isAdded(SAVER));
@@ -82,7 +85,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testSetAdded() {
mAutoTracker = new AutoAddTracker(mContext);
mAutoTracker = new AutoAddTracker(mContext, USER);
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
@@ -94,16 +97,35 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testPersist() {
mAutoTracker = new AutoAddTracker(mContext);
mAutoTracker = new AutoAddTracker(mContext, USER);
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
mAutoTracker.destroy();
mAutoTracker = new AutoAddTracker(mContext);
mAutoTracker = new AutoAddTracker(mContext, USER);
assertTrue(mAutoTracker.isAdded(SAVER));
mAutoTracker.destroy();
}
@Test
public void testIndependentUsers() {
mAutoTracker = new AutoAddTracker(mContext, USER);
mAutoTracker.setTileAdded(SAVER);
mAutoTracker = new AutoAddTracker(mContext, USER + 1);
assertFalse(mAutoTracker.isAdded(SAVER));
}
@Test
public void testChangeUser() {
mAutoTracker = new AutoAddTracker(mContext, USER);
mAutoTracker.setTileAdded(SAVER);
mAutoTracker = new AutoAddTracker(mContext, USER + 1);
mAutoTracker.changeUser(UserHandle.of(USER));
assertTrue(mAutoTracker.isAdded(SAVER));
}
}

View File

@@ -16,18 +16,34 @@
package com.android.systemui.statusbar.phone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContentResolver;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -38,14 +54,18 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.statusbar.phone.AutoTileManagerTest.MyContextWrapper;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.HotspotController;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,9 +84,18 @@ public class AutoTileManagerTest extends SysuiTestCase {
private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
private static final int USER = 0;
@Mock private QSTileHost mQsTileHost;
@Mock private AutoAddTracker mAutoAddTracker;
@Mock private CastController mCastController;
@Mock private HotspotController mHotspotController;
@Mock private DataSaverController mDataSaverController;
@Mock private ManagedProfileController mManagedProfileController;
@Mock private NightDisplayListener mNightDisplayListener;
@Mock(answer = Answers.RETURNS_SELF)
private AutoAddTracker.Builder mAutoAddTrackerBuilder;
@Mock private Context mUserContext;
private AutoTileManager mAutoTileManager;
@@ -82,19 +111,113 @@ public class AutoTileManagerTest extends SysuiTestCase {
}
);
mAutoTileManager = createAutoTileManager();
when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
mAutoTileManager = createAutoTileManager(new
MyContextWrapper(mContext));
}
private AutoTileManager createAutoTileManager() {
return new AutoTileManager(mContext, mAutoAddTracker, mQsTileHost,
@After
public void tearDown() {
mAutoTileManager.destroy();
}
private AutoTileManager createAutoTileManager(Context context) {
return new AutoTileManager(context, mAutoAddTrackerBuilder, mQsTileHost,
Handler.createAsync(TestableLooper.get(this).getLooper()),
mock(HotspotController.class),
mock(DataSaverController.class),
mock(ManagedProfileController.class),
mock(NightDisplayListener.class),
mHotspotController,
mDataSaverController,
mManagedProfileController,
mNightDisplayListener,
mCastController);
}
@Test
public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
TestableLooper.get(this).runWithLooper(() ->
mAutoTileManager.changeUser(UserHandle.of(USER + 1))
);
InOrder inOrderHotspot = inOrder(mHotspotController);
inOrderHotspot.verify(mHotspotController).removeCallback(any());
inOrderHotspot.verify(mHotspotController).addCallback(any());
InOrder inOrderDataSaver = inOrder(mDataSaverController);
inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
inOrderDataSaver.verify(mDataSaverController).addCallback(any());
InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
}
InOrder inOrderCast = inOrder(mCastController);
inOrderCast.verify(mCastController).removeCallback(any());
inOrderCast.verify(mCastController).addCallback(any());
SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertTrue(setting.isListening());
}
@Test
public void testChangeUserSomeCallbacksNotAdded() throws Exception {
when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
when(mAutoAddTracker.isAdded("work")).thenReturn(true);
when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
TestableLooper.get(this).runWithLooper(() ->
mAutoTileManager.changeUser(UserHandle.of(USER + 1))
);
verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
InOrder inOrderHotspot = inOrder(mHotspotController);
inOrderHotspot.verify(mHotspotController).removeCallback(any());
inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
InOrder inOrderDataSaver = inOrder(mDataSaverController);
inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
inOrderDataSaver.verify(mDataSaverController).addCallback(any());
InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
}
InOrder inOrderCast = inOrder(mCastController);
inOrderCast.verify(mCastController).removeCallback(any());
inOrderCast.verify(mCastController, never()).addCallback(any());
SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertFalse(setting.isListening());
}
@Test
public void testGetCurrentUserId() throws Exception {
assertEquals(USER, mAutoTileManager.getCurrentUserId());
TestableLooper.get(this).runWithLooper(() ->
mAutoTileManager.changeUser(UserHandle.of(USER + 100))
);
assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
}
@Test
public void nightTileAdded_whenActivated() {
if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
@@ -213,14 +336,14 @@ public class AutoTileManagerTest extends SysuiTestCase {
public void testEmptyArray_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd, new String[0]);
createAutoTileManager();
createAutoTileManager(mContext).destroy();
}
@Test
public void testMissingConfig_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd, null);
createAutoTileManager();
createAutoTileManager(mContext).destroy();
}
// Will only notify if it's listening
@@ -231,4 +354,22 @@ public class AutoTileManagerTest extends SysuiTestCase {
s.onChange(false);
}
}
class MyContextWrapper extends ContextWrapper {
private TestableContentResolver mSpiedTCR;
MyContextWrapper(TestableContext context) {
super(context);
mSpiedTCR = spy(context.getContentResolver());
doNothing().when(mSpiedTCR).registerContentObserver(any(), anyBoolean(), any(),
anyInt());
doNothing().when(mSpiedTCR).unregisterContentObserver(any());
}
@Override
public ContentResolver getContentResolver() {
return mSpiedTCR;
}
}
}