Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f05818a authored by Keun-young Park's avatar Keun-young Park Committed by Android (Google) Code Review
Browse files

Merge "Add multiuserDelayUserDataLocking configuration"

parents 78b95204 df54b66b
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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>

+1 −0
Original line number Diff line number Diff line
@@ -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" />
+2 −0
Original line number Diff line number Diff line
@@ -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;
        }
+68 −9
Original line number Diff line number Diff line
@@ -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));
    }
@@ -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) {
@@ -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);
@@ -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();
                }
@@ -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}.
@@ -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);
    }
@@ -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);
@@ -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;
        }
@@ -2033,6 +2086,8 @@ class UserController implements Handler.Callback {
                    pw.println(mUserProfileGroupIds.valueAt(i));
                }
            }
            pw.println("  mCurrentUserId:" + mCurrentUserId);
            pw.println("  mLastActiveUsers:" + mLastActiveUsers);
        }
    }

@@ -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"));
        }
    }
}
+170 −8
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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,
@@ -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);
        });
@@ -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);
@@ -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);
@@ -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
@@ -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);
@@ -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;
@@ -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) {
@@ -367,6 +523,7 @@ public class UserControllerTest {
            mUserManagerMock = mock(UserManagerService.class);
            mUserManagerInternalMock = mock(UserManagerInternal.class);
            mWindowManagerMock = mock(WindowManagerService.class);
            mStorageManagerMock = mock(IStorageManager.class);
        }

        @Override
@@ -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 {