Loading core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -2442,6 +2442,16 @@ <!-- Maximum number of users we allow to be running at a time --> <integer name="config_multiuserMaxRunningUsers">3</integer> <!-- Whether to delay user data locking for background user. If false, user switched-out from user switching will still be in running state until config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is reached, user will be stopped and user data is locked. If true, user switched out from user switching will always be stopped but its user data is not locked. Total number of unlocked users will be limited by config_multiuserMaxRunningUsers. Once that limit is reached, least recently stopped user will be locked. --> <bool name="config_multiuserDelayUserDataLocking">false</bool> <!-- Whether UI for multi user should be shown --> <bool name="config_enableMultiUserUI">false</bool> Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -498,6 +498,7 @@ <java-symbol type="integer" name="config_lockSoundVolumeDb" /> <java-symbol type="integer" name="config_multiuserMaximumUsers" /> <java-symbol type="integer" name="config_multiuserMaxRunningUsers" /> <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> <java-symbol type="integer" name="config_safe_media_volume_index" /> <java-symbol type="integer" name="config_safe_media_volume_usb_mB" /> <java-symbol type="integer" name="config_mobile_mtu" /> Loading services/core/java/com/android/server/am/ActivityManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -8772,6 +8772,8 @@ public class ActivityManagerService extends IActivityManager.Stub com.android.internal.R.bool.config_customUserSwitchUi); mUserController.mMaxRunningUsers = res.getInteger( com.android.internal.R.integer.config_multiuserMaxRunningUsers); mUserController.mDelayUserDataLocking = res.getBoolean( com.android.internal.R.bool.config_multiuserDelayUserDataLocking); mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; } Loading services/core/java/com/android/server/am/UserController.java +68 −9 Original line number Diff line number Diff line Loading @@ -241,6 +241,20 @@ class UserController implements Handler.Callback { volatile boolean mBootCompleted; /** * In this mode, user is always stopped when switched out but locking of user data is * postponed until total number of unlocked users in the system reaches mMaxRunningUsers. * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user * will be locked. */ boolean mDelayUserDataLocking; /** * Keep track of last active users for mDelayUserDataLocking. * The latest stopped user is placed in front while the least recently stopped user in back. */ @GuardedBy("mLock") private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); UserController(ActivityManagerService service) { this(new Injector(service)); } Loading Loading @@ -738,7 +752,9 @@ class UserController implements Handler.Callback { void finishUserStopped(UserState uss) { final int userId = uss.mHandle.getIdentifier(); final boolean stopped; boolean lockUser = true; ArrayList<IStopUserCallback> callbacks; int userIdToLock = userId; synchronized (mLock) { callbacks = new ArrayList<>(uss.mStopCallbacks); if (mStartedUsers.get(userId) != uss || uss.state != UserState.STATE_SHUTDOWN) { Loading @@ -749,9 +765,12 @@ class UserController implements Handler.Callback { mStartedUsers.remove(userId); mUserLru.remove(Integer.valueOf(userId)); updateStartedUserArrayLU(); userIdToLock = updateUserToLockLU(userId); if (userIdToLock == UserHandle.USER_NULL) { lockUser = false; } } } if (stopped) { mInjector.getUserManagerInternal().removeUserState(userId); mInjector.activityManagerOnUserStopped(userId); Loading @@ -776,18 +795,22 @@ class UserController implements Handler.Callback { mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); } if (!lockUser) { return; } final int userIdToLockF = userIdToLock; // Evict the user's credential encryption key. Performed on FgThread to make it // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking // to prevent data corruption. FgThread.getHandler().post(() -> { synchronized (mLock) { if (mStartedUsers.get(userId) != null) { if (mStartedUsers.get(userIdToLockF) != null) { Slog.w(TAG, "User was restarted, skipping key eviction"); return; } } try { getStorageManager().lockUserKey(userId); mInjector.getStorageManager().lockUserKey(userIdToLockF); } catch (RemoteException re) { throw re.rethrowAsRuntimeException(); } Loading @@ -795,6 +818,39 @@ class UserController implements Handler.Callback { } } /** * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked. * Total number of unlocked user storage is limited by mMaxRunningUsers. * If there are more unlocked users, evict and lock the least recently stopped user and * lock that user's data. Regardless of the mode, ephemeral user is always locked * immediately. * * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked. */ @GuardedBy("mLock") private int updateUserToLockLU(int userId) { int userIdToLock = userId; if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral() && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { mLastActiveUsers.remove((Integer) userId); // arg should be object, not index mLastActiveUsers.add(0, userId); int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size(); if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1); mLastActiveUsers.remove(mLastActiveUsers.size() - 1); Slog.i(TAG, "finishUserStopped, stopping user:" + userId + " lock user:" + userIdToLock); } else { Slog.i(TAG, "finishUserStopped, user:" + userId + ",skip locking"); // do not lock userIdToLock = UserHandle.USER_NULL; } } return userIdToLock; } /** * Determines the list of users that should be stopped together with the specified * {@code userId}. The returned list includes {@code userId}. Loading Loading @@ -896,9 +952,6 @@ class UserController implements Handler.Callback { } } private IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } boolean startUser(final int userId, final boolean foreground) { return startUser(userId, foreground, null); } Loading Loading @@ -1199,7 +1252,7 @@ class UserController implements Handler.Callback { UserState uss; if (!StorageManager.isUserKeyUnlocked(userId)) { final UserInfo userInfo = getUserInfo(userId); final IStorageManager storageManager = getStorageManager(); final IStorageManager storageManager = mInjector.getStorageManager(); try { // We always want to unlock user storage, even user is not started yet storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret); Loading Loading @@ -1334,9 +1387,9 @@ class UserController implements Handler.Callback { if (oldUserId == UserHandle.USER_SYSTEM) { return; } // For now, only check for user restriction. Additional checks can be added here // If running in background is disabled or mDelayUserDataLocking mode, stop the user. boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); oldUserId) || mDelayUserDataLocking; if (!disallowRunInBg) { return; } Loading Loading @@ -2033,6 +2086,8 @@ class UserController implements Handler.Callback { pw.println(mUserProfileGroupIds.valueAt(i)); } } pw.println(" mCurrentUserId:" + mCurrentUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); } } Loading Loading @@ -2326,5 +2381,9 @@ class UserController implements Handler.Callback { protected boolean isCallerRecents(int callingUid) { return mService.mAtmInternal.isCallerRecents(callingUid); } protected IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } } } services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +170 −8 Original line number Diff line number Diff line Loading @@ -68,12 +68,15 @@ import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.FgThread; import com.android.server.pm.UserManagerService; import com.android.server.wm.WindowManagerService; Loading @@ -82,7 +85,9 @@ import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; Loading @@ -95,12 +100,20 @@ import java.util.Set; */ @SmallTest @Presubmit public class UserControllerTest { private static final int TEST_USER_ID = 10; // Use big enough user id to avoid picking up already active user id. private static final int TEST_USER_ID = 100; private static final int TEST_USER_ID1 = 101; private static final int TEST_USER_ID2 = 102; private static final int NONEXIST_USER_ID = 2; private static final String TAG = UserControllerTest.class.getSimpleName(); private static final long HANDLER_WAIT_TIME_MS = 100; private UserController mUserController; private TestInjector mInjector; private final HashMap<Integer, UserState> mUserStates = new HashMap<>(); private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList( Intent.ACTION_USER_STARTED, Loading Loading @@ -130,6 +143,11 @@ public class UserControllerTest { doNothing().when(mInjector).startHomeActivity(anyInt(), anyString()); doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any()); doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity(); doNothing().when(mInjector).systemServiceManagerCleanupUser(anyInt()); doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString()); doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt()); mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, 0); }); Loading Loading @@ -261,7 +279,7 @@ public class UserControllerTest { } @Test public void testContinueUserSwitch() { public void testContinueUserSwitch() throws RemoteException { // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); Loading @@ -273,11 +291,11 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); continueUserSwitchAssertions(); continueUserSwitchAssertions(TEST_USER_ID, false); } @Test public void testContinueUserSwitchUIDisabled() { public void testContinueUserSwitchUIDisabled() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, true); Loading @@ -290,16 +308,21 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); continueUserSwitchAssertions(); continueUserSwitchAssertions(TEST_USER_ID, false); } private void continueUserSwitchAssertions() { Set<Integer> expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG); private void continueUserSwitchAssertions(int expectedUserId, boolean backgroundUserStopping) throws RemoteException { Set<Integer> expectedCodes = new LinkedHashSet<>(); expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(0); // this is for directly posting in stopping. } Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); assertEquals("Unexpected message sent", expectedCodes, actualCodes); Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); assertNotNull(msg); assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1); assertEquals("Unexpected userId", expectedUserId, msg.arg1); } @Test Loading Loading @@ -332,6 +355,120 @@ public class UserControllerTest { assertTrue(mUserController.isSystemUserStarted()); } /** * Test stopping of user from max running users limit. */ @Test public void testUserStoppingForMultipleUsersNormalMode() throws InterruptedException, RemoteException { setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); mUserController.mMaxRunningUsers = 3; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, numerOfUserSwitches, false); // running: user 0, USER_ID, USER_ID1 assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, numerOfUserSwitches, false); UserState ussUser2 = mUserStates.get(TEST_USER_ID2); // skip middle step and call this directly. mUserController.finishUserSwitch(ussUser2); waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); // running: user 0, USER_ID1, USER_ID2 // USER_ID should be stopped as it is least recently used non user0. assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}), mUserController.getRunningUsersLU()); } /** * This test tests delayed locking mode using 4 users. As core logic of delayed locking is * happening in finishUserStopped call, the test also calls finishUserStopped while skipping * all middle steps which takes too much work to mock. */ @Test public void testUserStoppingForMultipleUsersDelayedLockingMode() throws InterruptedException, RemoteException { setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); mUserController.mMaxRunningUsers = 3; mUserController.mDelayUserDataLocking = true; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, numerOfUserSwitches, true); // running: user 0, USER_ID1 // stopped + unlocked: USER_ID numerOfUserSwitches++; assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1}), mUserController.getRunningUsersLU()); // Skip all other steps and test unlock delaying only UserState uss = mUserStates.get(TEST_USER_ID); uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part mUserController.finishUserStopped(uss); // Cannot mock FgThread handler, so confirm that there is no posted message left before // checking. waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(0)) .lockUserKey(anyInt()); addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, numerOfUserSwitches, true); // running: user 0, USER_ID2 // stopped + unlocked: USER_ID1 // stopped + locked: USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID2}), mUserController.getRunningUsersLU()); UserState ussUser1 = mUserStates.get(TEST_USER_ID1); ussUser1.setState(UserState.STATE_SHUTDOWN); mUserController.finishUserStopped(ussUser1); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(1)) .lockUserKey(TEST_USER_ID); } private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId, int expectedNumberOfCalls, boolean expectOldUserStopping) throws RemoteException { // Start user -- this will update state of mUserController mUserController.startUser(newUserId, true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; int oldUserId = reportMsg.arg1; assertEquals(expectedOldUserId, oldUserId); assertEquals(newUserId, reportMsg.arg2); mUserStates.put(newUserId, userState); mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), times(expectedNumberOfCalls)) .stopFreezingScreen(); continueUserSwitchAssertions(newUserId, expectOldUserStopping); } private void setUpUser(int userId, int flags) { UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); Loading @@ -345,6 +482,22 @@ public class UserControllerTest { return result; } private void waitForHandlerToComplete(Handler handler, long waitTimeMs) throws InterruptedException { if (!handler.hasMessagesOrCallbacks()) { // if nothing queued, do not wait. return; } final Object lock = new Object(); synchronized (lock) { handler.post(() -> { synchronized (lock) { lock.notify(); } }); lock.wait(waitTimeMs); } } // Should be public to allow mocking private static class TestInjector extends UserController.Injector { public final TestHandler mHandler; Loading @@ -353,8 +506,11 @@ public class UserControllerTest { public final List<Intent> mSentIntents = new ArrayList<>(); private final TestHandler mUiHandler; private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; private final Context mCtx; TestInjector(Context ctx) { Loading @@ -367,6 +523,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); mStorageManagerMock = mock(IStorageManager.class); } @Override Loading Loading @@ -434,6 +591,11 @@ public class UserControllerTest { // to pass all metrics related calls return true; } @Override protected IStorageManager getStorageManager() { return mStorageManagerMock; } } private static class TestHandler extends Handler { Loading Loading
core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -2442,6 +2442,16 @@ <!-- Maximum number of users we allow to be running at a time --> <integer name="config_multiuserMaxRunningUsers">3</integer> <!-- Whether to delay user data locking for background user. If false, user switched-out from user switching will still be in running state until config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is reached, user will be stopped and user data is locked. If true, user switched out from user switching will always be stopped but its user data is not locked. Total number of unlocked users will be limited by config_multiuserMaxRunningUsers. Once that limit is reached, least recently stopped user will be locked. --> <bool name="config_multiuserDelayUserDataLocking">false</bool> <!-- Whether UI for multi user should be shown --> <bool name="config_enableMultiUserUI">false</bool> Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -498,6 +498,7 @@ <java-symbol type="integer" name="config_lockSoundVolumeDb" /> <java-symbol type="integer" name="config_multiuserMaximumUsers" /> <java-symbol type="integer" name="config_multiuserMaxRunningUsers" /> <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> <java-symbol type="integer" name="config_safe_media_volume_index" /> <java-symbol type="integer" name="config_safe_media_volume_usb_mB" /> <java-symbol type="integer" name="config_mobile_mtu" /> Loading
services/core/java/com/android/server/am/ActivityManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -8772,6 +8772,8 @@ public class ActivityManagerService extends IActivityManager.Stub com.android.internal.R.bool.config_customUserSwitchUi); mUserController.mMaxRunningUsers = res.getInteger( com.android.internal.R.integer.config_multiuserMaxRunningUsers); mUserController.mDelayUserDataLocking = res.getBoolean( com.android.internal.R.bool.config_multiuserDelayUserDataLocking); mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; } Loading
services/core/java/com/android/server/am/UserController.java +68 −9 Original line number Diff line number Diff line Loading @@ -241,6 +241,20 @@ class UserController implements Handler.Callback { volatile boolean mBootCompleted; /** * In this mode, user is always stopped when switched out but locking of user data is * postponed until total number of unlocked users in the system reaches mMaxRunningUsers. * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user * will be locked. */ boolean mDelayUserDataLocking; /** * Keep track of last active users for mDelayUserDataLocking. * The latest stopped user is placed in front while the least recently stopped user in back. */ @GuardedBy("mLock") private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); UserController(ActivityManagerService service) { this(new Injector(service)); } Loading Loading @@ -738,7 +752,9 @@ class UserController implements Handler.Callback { void finishUserStopped(UserState uss) { final int userId = uss.mHandle.getIdentifier(); final boolean stopped; boolean lockUser = true; ArrayList<IStopUserCallback> callbacks; int userIdToLock = userId; synchronized (mLock) { callbacks = new ArrayList<>(uss.mStopCallbacks); if (mStartedUsers.get(userId) != uss || uss.state != UserState.STATE_SHUTDOWN) { Loading @@ -749,9 +765,12 @@ class UserController implements Handler.Callback { mStartedUsers.remove(userId); mUserLru.remove(Integer.valueOf(userId)); updateStartedUserArrayLU(); userIdToLock = updateUserToLockLU(userId); if (userIdToLock == UserHandle.USER_NULL) { lockUser = false; } } } if (stopped) { mInjector.getUserManagerInternal().removeUserState(userId); mInjector.activityManagerOnUserStopped(userId); Loading @@ -776,18 +795,22 @@ class UserController implements Handler.Callback { mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); } if (!lockUser) { return; } final int userIdToLockF = userIdToLock; // Evict the user's credential encryption key. Performed on FgThread to make it // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking // to prevent data corruption. FgThread.getHandler().post(() -> { synchronized (mLock) { if (mStartedUsers.get(userId) != null) { if (mStartedUsers.get(userIdToLockF) != null) { Slog.w(TAG, "User was restarted, skipping key eviction"); return; } } try { getStorageManager().lockUserKey(userId); mInjector.getStorageManager().lockUserKey(userIdToLockF); } catch (RemoteException re) { throw re.rethrowAsRuntimeException(); } Loading @@ -795,6 +818,39 @@ class UserController implements Handler.Callback { } } /** * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked. * Total number of unlocked user storage is limited by mMaxRunningUsers. * If there are more unlocked users, evict and lock the least recently stopped user and * lock that user's data. Regardless of the mode, ephemeral user is always locked * immediately. * * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked. */ @GuardedBy("mLock") private int updateUserToLockLU(int userId) { int userIdToLock = userId; if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral() && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { mLastActiveUsers.remove((Integer) userId); // arg should be object, not index mLastActiveUsers.add(0, userId); int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size(); if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1); mLastActiveUsers.remove(mLastActiveUsers.size() - 1); Slog.i(TAG, "finishUserStopped, stopping user:" + userId + " lock user:" + userIdToLock); } else { Slog.i(TAG, "finishUserStopped, user:" + userId + ",skip locking"); // do not lock userIdToLock = UserHandle.USER_NULL; } } return userIdToLock; } /** * Determines the list of users that should be stopped together with the specified * {@code userId}. The returned list includes {@code userId}. Loading Loading @@ -896,9 +952,6 @@ class UserController implements Handler.Callback { } } private IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } boolean startUser(final int userId, final boolean foreground) { return startUser(userId, foreground, null); } Loading Loading @@ -1199,7 +1252,7 @@ class UserController implements Handler.Callback { UserState uss; if (!StorageManager.isUserKeyUnlocked(userId)) { final UserInfo userInfo = getUserInfo(userId); final IStorageManager storageManager = getStorageManager(); final IStorageManager storageManager = mInjector.getStorageManager(); try { // We always want to unlock user storage, even user is not started yet storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret); Loading Loading @@ -1334,9 +1387,9 @@ class UserController implements Handler.Callback { if (oldUserId == UserHandle.USER_SYSTEM) { return; } // For now, only check for user restriction. Additional checks can be added here // If running in background is disabled or mDelayUserDataLocking mode, stop the user. boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); oldUserId) || mDelayUserDataLocking; if (!disallowRunInBg) { return; } Loading Loading @@ -2033,6 +2086,8 @@ class UserController implements Handler.Callback { pw.println(mUserProfileGroupIds.valueAt(i)); } } pw.println(" mCurrentUserId:" + mCurrentUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); } } Loading Loading @@ -2326,5 +2381,9 @@ class UserController implements Handler.Callback { protected boolean isCallerRecents(int callingUid) { return mService.mAtmInternal.isCallerRecents(callingUid); } protected IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } } }
services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +170 −8 Original line number Diff line number Diff line Loading @@ -68,12 +68,15 @@ import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.FgThread; import com.android.server.pm.UserManagerService; import com.android.server.wm.WindowManagerService; Loading @@ -82,7 +85,9 @@ import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; Loading @@ -95,12 +100,20 @@ import java.util.Set; */ @SmallTest @Presubmit public class UserControllerTest { private static final int TEST_USER_ID = 10; // Use big enough user id to avoid picking up already active user id. private static final int TEST_USER_ID = 100; private static final int TEST_USER_ID1 = 101; private static final int TEST_USER_ID2 = 102; private static final int NONEXIST_USER_ID = 2; private static final String TAG = UserControllerTest.class.getSimpleName(); private static final long HANDLER_WAIT_TIME_MS = 100; private UserController mUserController; private TestInjector mInjector; private final HashMap<Integer, UserState> mUserStates = new HashMap<>(); private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList( Intent.ACTION_USER_STARTED, Loading Loading @@ -130,6 +143,11 @@ public class UserControllerTest { doNothing().when(mInjector).startHomeActivity(anyInt(), anyString()); doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any()); doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity(); doNothing().when(mInjector).systemServiceManagerCleanupUser(anyInt()); doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString()); doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt()); mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, 0); }); Loading Loading @@ -261,7 +279,7 @@ public class UserControllerTest { } @Test public void testContinueUserSwitch() { public void testContinueUserSwitch() throws RemoteException { // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); Loading @@ -273,11 +291,11 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); continueUserSwitchAssertions(); continueUserSwitchAssertions(TEST_USER_ID, false); } @Test public void testContinueUserSwitchUIDisabled() { public void testContinueUserSwitchUIDisabled() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, true); Loading @@ -290,16 +308,21 @@ public class UserControllerTest { // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); continueUserSwitchAssertions(); continueUserSwitchAssertions(TEST_USER_ID, false); } private void continueUserSwitchAssertions() { Set<Integer> expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG); private void continueUserSwitchAssertions(int expectedUserId, boolean backgroundUserStopping) throws RemoteException { Set<Integer> expectedCodes = new LinkedHashSet<>(); expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(0); // this is for directly posting in stopping. } Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); assertEquals("Unexpected message sent", expectedCodes, actualCodes); Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); assertNotNull(msg); assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1); assertEquals("Unexpected userId", expectedUserId, msg.arg1); } @Test Loading Loading @@ -332,6 +355,120 @@ public class UserControllerTest { assertTrue(mUserController.isSystemUserStarted()); } /** * Test stopping of user from max running users limit. */ @Test public void testUserStoppingForMultipleUsersNormalMode() throws InterruptedException, RemoteException { setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); mUserController.mMaxRunningUsers = 3; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, numerOfUserSwitches, false); // running: user 0, USER_ID, USER_ID1 assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, numerOfUserSwitches, false); UserState ussUser2 = mUserStates.get(TEST_USER_ID2); // skip middle step and call this directly. mUserController.finishUserSwitch(ussUser2); waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS); // running: user 0, USER_ID1, USER_ID2 // USER_ID should be stopped as it is least recently used non user0. assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}), mUserController.getRunningUsersLU()); } /** * This test tests delayed locking mode using 4 users. As core logic of delayed locking is * happening in finishUserStopped call, the test also calls finishUserStopped while skipping * all middle steps which takes too much work to mock. */ @Test public void testUserStoppingForMultipleUsersDelayedLockingMode() throws InterruptedException, RemoteException { setUpUser(TEST_USER_ID1, 0); setUpUser(TEST_USER_ID2, 0); mUserController.mMaxRunningUsers = 3; mUserController.mDelayUserDataLocking = true; int numerOfUserSwitches = 1; addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM, numerOfUserSwitches, false); // running: user 0, USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}), mUserController.getRunningUsersLU()); numerOfUserSwitches++; addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID, numerOfUserSwitches, true); // running: user 0, USER_ID1 // stopped + unlocked: USER_ID numerOfUserSwitches++; assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1}), mUserController.getRunningUsersLU()); // Skip all other steps and test unlock delaying only UserState uss = mUserStates.get(TEST_USER_ID); uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part mUserController.finishUserStopped(uss); // Cannot mock FgThread handler, so confirm that there is no posted message left before // checking. waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(0)) .lockUserKey(anyInt()); addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1, numerOfUserSwitches, true); // running: user 0, USER_ID2 // stopped + unlocked: USER_ID1 // stopped + locked: USER_ID assertTrue(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID2}), mUserController.getRunningUsersLU()); UserState ussUser1 = mUserStates.get(TEST_USER_ID1); ussUser1.setState(UserState.STATE_SHUTDOWN); mUserController.finishUserStopped(ussUser1); waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS); verify(mInjector.mStorageManagerMock, times(1)) .lockUserKey(TEST_USER_ID); } private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId, int expectedNumberOfCalls, boolean expectOldUserStopping) throws RemoteException { // Start user -- this will update state of mUserController mUserController.startUser(newUserId, true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; int oldUserId = reportMsg.arg1; assertEquals(expectedOldUserId, oldUserId); assertEquals(newUserId, reportMsg.arg2); mUserStates.put(newUserId, userState); mInjector.mHandler.clearAllRecordedMessages(); // Verify that continueUserSwitch worked as expected mUserController.continueUserSwitch(userState, oldUserId, newUserId); verify(mInjector.getWindowManager(), times(expectedNumberOfCalls)) .stopFreezingScreen(); continueUserSwitchAssertions(newUserId, expectOldUserStopping); } private void setUpUser(int userId, int flags) { UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); Loading @@ -345,6 +482,22 @@ public class UserControllerTest { return result; } private void waitForHandlerToComplete(Handler handler, long waitTimeMs) throws InterruptedException { if (!handler.hasMessagesOrCallbacks()) { // if nothing queued, do not wait. return; } final Object lock = new Object(); synchronized (lock) { handler.post(() -> { synchronized (lock) { lock.notify(); } }); lock.wait(waitTimeMs); } } // Should be public to allow mocking private static class TestInjector extends UserController.Injector { public final TestHandler mHandler; Loading @@ -353,8 +506,11 @@ public class UserControllerTest { public final List<Intent> mSentIntents = new ArrayList<>(); private final TestHandler mUiHandler; private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; private final Context mCtx; TestInjector(Context ctx) { Loading @@ -367,6 +523,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); mStorageManagerMock = mock(IStorageManager.class); } @Override Loading Loading @@ -434,6 +591,11 @@ public class UserControllerTest { // to pass all metrics related calls return true; } @Override protected IStorageManager getStorageManager() { return mStorageManagerMock; } } private static class TestHandler extends Handler { Loading