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

Commit 1c1baa7e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I318df5a4,Id626771c into main

* changes:
  getUserLogoutability to return error for unsupported device
  Logout API: handle edge case
parents 6fd431f0 2994f36f
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -2301,6 +2301,12 @@ public class UserManager {
     */
    public static final int LOGOUTABILITY_STATUS_CANNOT_SWITCH = 3;

    /**
     * Indicates that user cannot logout because logout is not supported on the device.
     * @hide
     */
    public static final int LOGOUTABILITY_STATUS_DEVICE_NOT_SUPPORTED = 4;

    /**
     * Result returned in {@link #getUserLogoutability()} indicating user logoutability.
     * @hide
@@ -2310,7 +2316,8 @@ public class UserManager {
            LOGOUTABILITY_STATUS_OK,
            LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER,
            LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO,
            LOGOUTABILITY_STATUS_CANNOT_SWITCH
            LOGOUTABILITY_STATUS_CANNOT_SWITCH,
            LOGOUTABILITY_STATUS_DEVICE_NOT_SUPPORTED
    })
    public @interface UserLogoutability {}

@@ -2807,6 +2814,7 @@ public class UserManager {
     * {@link #LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER},
     * {@link #LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO},
     * {@link #LOGOUTABILITY_STATUS_CANNOT_SWITCH}.
     * {@link #LOGOUTABILITY_STATUS_DEVICE_NOT_SUPPORTED}.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_USERS)
+37 −8
Original line number Diff line number Diff line
@@ -286,6 +286,12 @@ class UserController implements Handler.Callback {
    @GuardedBy("mLock")
    private final SparseArray<UserState> mStartedUsers = new SparseArray<>();

    // If logging out of a user cannot be immediately done, because we are in the process of
    // switching to that same user and cannot stop that user, then we flip this flag to true, so
    // that when the ongoing user-switch is finished, we would stop the user then.
    @GuardedBy("mLock")
    private boolean mPendingLogoutTargetUser = false;

    /**
     * LRU list of history of running users, in order of when we last needed to start them.
     *
@@ -987,27 +993,40 @@ class UserController implements Handler.Callback {
     *   <li>The stop user operation fails for any reason.
     * </ul>
     *
     * <p>The logoutUser API does not support HSUM devices that cannot switch to system user.
     *
     * @see ActivityManager#logoutUser(int)
     * @param userId The ID of the user to log out.
     * @return true if logout is successfully initiated.
     */
    boolean logoutUser(@UserIdInt int userId) {
        // TODO(b/380125011): To handle the edge case of trying to logout the user that the device
        // is in the process of switching to (i.e. userId == mTargetUserId), we had to logout to the
        // system user (not the previous foreground user). Thus we cannot support HSUM devices
        // without interactive headless system user. To solve it for the long term, a refactor might
        // be needed, see more details in the bug.
        if (mInjector.isHeadlessSystemUserMode()
                && !mInjector.getUserManager().canSwitchToHeadlessSystemUser()) {
            throw new UnsupportedOperationException("device does not support logoutUser");
        }
        boolean shouldSwitchUser = false;
        synchronized (mLock) {
            if (userId == UserHandle.USER_SYSTEM) {
                Slogf.e(TAG, "Cannot logout system user %d", userId);
                return false;
            }
            if (userId == mTargetUserId) {
                // TODO(b/380125011): Properly handle this case, rather than returning failure.
                Slogf.e(TAG, "Cannot logout user %d as we're in the process of switching to it, and"
                        + " therefore logout has failed.", userId);
                return false;
            }
            // Since we are logging out of userId, let's not switch to userId (after possible
            // ongoing user switch).
            mPendingTargetUserIds.removeIf(id -> id == userId);

            if (userId == mTargetUserId) {
                Slogf.i(TAG, "Cannot logout user %d now as we're in the process of switching to it,"
                        + " it will be logged out when the ongoing user-switch is complete.",
                        userId);
                mPendingLogoutTargetUser = true;
                return true;
            }

            if (userId == getCurrentUserId() && mTargetUserId == UserHandle.USER_NULL) {
                shouldSwitchUser = true;
            }
@@ -1036,7 +1055,8 @@ class UserController implements Handler.Callback {
        }
        // Now, userId is not current, so we can simply stop it. Attempting to stop system user
        // will fail.
        final int result = stopUser(userId, false, null, null);
        final int result = stopUser(userId, /* allowDelayedLocking= */ false,
                /* stopUserCallback= */ null, /* keyEvictedCallback */ null);
        if (result != USER_OP_SUCCESS) {
            Slogf.e(TAG, "Cannot logout user %d; stop user failed with result %d", userId, result);
            return false;
@@ -2435,14 +2455,22 @@ class UserController implements Handler.Callback {
            mInjector.setPerformancePowerMode(false);
        }
        final int nextUserId;
        final int stopUserId;
        synchronized (mLock) {
            nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
            nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(),
                    mPendingLogoutTargetUser ? UserHandle.USER_SYSTEM : UserHandle.USER_NULL);
            stopUserId = mPendingLogoutTargetUser ? mTargetUserId : UserHandle.USER_NULL;
            mPendingLogoutTargetUser = false;
            mTargetUserId = UserHandle.USER_NULL;
            ActivityManager.invalidateGetCurrentUserIdCache();
        }
        if (nextUserId != UserHandle.USER_NULL) {
            switchUser(nextUserId);
        }
        if (stopUserId != UserHandle.USER_NULL) {
            stopUser(stopUserId, /* allowDelayedLocking= */ false, /* stopUserCallback= */ null,
                    /* keyEvictedCallback= */ null);
        }
    }

    private void dispatchLockedBootComplete(@UserIdInt int userId) {
@@ -3588,6 +3616,7 @@ class UserController implements Handler.Callback {
            pw.println("  mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
            pw.println("  mCurrentUserId:" + mCurrentUserId);
            pw.println("  mTargetUserId:" + mTargetUserId);
            pw.println("  mPendingLogoutTargetUser:" + mPendingLogoutTargetUser);
            pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
            pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
            pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
+4 −7
Original line number Diff line number Diff line
@@ -3052,15 +3052,12 @@ public class UserManagerService extends IUserManager.Stub {

        checkManageUsersPermission("getUserLogoutability");

        if (userId == UserHandle.USER_SYSTEM) {
            return UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER;
        if (isHeadlessSystemUserMode() && !canSwitchToHeadlessSystemUser()) {
            return UserManager.LOGOUTABILITY_STATUS_DEVICE_NOT_SUPPORTED;
        }

        if (userId != getCurrentUserId()) {
            // TODO(b/393656514): Decide what to do with non-current/background users.
            // As of now, we are not going to logout a background user. A background user should
            // simply be stopped instead.
            return UserManager.LOGOUTABILITY_STATUS_CANNOT_SWITCH;
        if (userId == UserHandle.USER_SYSTEM) {
            return UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER;
        }

        if (getUserSwitchability(userId) != UserManager.SWITCHABILITY_STATUS_OK) {
+24 −5
Original line number Diff line number Diff line
@@ -78,6 +78,8 @@ public final class UserControllerMockedTest {

    @Test
    public void testLogoutUser_HsumAndInteractiveUser0_CanLogoutCurrentUser() {
        mockSystemUserHeadlessMode(true);
        mockCanSwitchToHeadlessSystemUser(true);
        mockCurrentUser(TEST_USER_1);
        when(mUserManagerInternal.getUserToLogoutCurrentUserTo())
                .thenReturn(UserHandle.USER_SYSTEM);
@@ -85,33 +87,41 @@ public final class UserControllerMockedTest {
        boolean result = mSpiedUserController.logoutUser(TEST_USER_1);

        assertThat(result).isTrue();
        verify(mSpiedUserController).switchUser(anyInt());
        verify(mSpiedUserController).switchUser(UserHandle.USER_SYSTEM);
        verify(mSpiedUserController).stopUser(TEST_USER_1, false, null, null);
    }

    @Test
    public void testLogoutUser_NonHsumOrNonInteractiveUser0_CanLogoutCurrentUser() {
    public void testLogoutUser_NonHsum_CanLogoutCurrentUser() {
        mockSystemUserHeadlessMode(false);
        mockCurrentUser(TEST_USER_1);
        when(mUserManagerInternal.getUserToLogoutCurrentUserTo()).thenReturn(TEST_USER_2);

        boolean result = mSpiedUserController.logoutUser(TEST_USER_1);

        assertThat(result).isTrue();
        verify(mSpiedUserController).switchUser(anyInt());
        verify(mSpiedUserController).switchUser(TEST_USER_2);
        verify(mSpiedUserController).stopUser(TEST_USER_1, false, null, null);
    }

    @Test
    public void testLogoutUser_LogoutNonCurrentUser_NoSwitchUser() {
        mockSystemUserHeadlessMode(true);
        mockCanSwitchToHeadlessSystemUser(true);
        mockCurrentUser(TEST_USER_1);

        boolean result = mSpiedUserController.logoutUser(TEST_USER_2);

        assertThat(result).isTrue();
        // Logout of non-current user does not need switch user.
        verify(mSpiedUserController, never()).switchUser(anyInt());
        // Logout of non-current user does not need switch user, but only stop user.
        verify(mSpiedUserController, never()).switchUser(UserHandle.USER_SYSTEM);
        verify(mSpiedUserController).stopUser(TEST_USER_2, false, null, null);
    }

    @Test
    public void testLogoutUser_CannotLogoutSystemUser() {
        mockSystemUserHeadlessMode(true);
        mockCanSwitchToHeadlessSystemUser(true);
        mockCurrentUser(UserHandle.USER_SYSTEM);

        boolean result = mSpiedUserController.logoutUser(UserHandle.USER_SYSTEM);
@@ -119,9 +129,18 @@ public final class UserControllerMockedTest {
        assertThat(result).isFalse();
        // No switch user should have happened.
        verify(mSpiedUserController, never()).switchUser(anyInt());
        verify(mSpiedUserController, never()).stopUser(UserHandle.USER_SYSTEM, false, null, null);
    }

    private void mockCurrentUser(@UserIdInt int userId) {
        when(mSpiedUserController.getCurrentUserId()).thenReturn(userId);
    }

    private void mockCanSwitchToHeadlessSystemUser(boolean canSwitch) {
        doReturn(canSwitch).when(mUserManagerService).canSwitchToHeadlessSystemUser();
    }

    private void mockSystemUserHeadlessMode(boolean headless) {
        when(mSpiedUserControllerInjector.isHeadlessSystemUserMode()).thenReturn(headless);
    }
}
+8 −4
Original line number Diff line number Diff line
@@ -964,7 +964,7 @@ public final class UserManagerServiceMockedTest {

    @Test
    @EnableFlags(FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystem_UserCanLogout()
    public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystemUser_UserCanLogout()
            throws Exception {
        setSystemUserHeadless(true);
        addUser(USER_ID);
@@ -980,7 +980,7 @@ public final class UserManagerServiceMockedTest {

    @Test
    @EnableFlags(FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystem_UserCannotLogout()
    public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystemUser_UserCannotLogout()
            throws Exception {
        setSystemUserHeadless(true);
        mockCanSwitchToHeadlessSystemUser(false);
@@ -990,13 +990,16 @@ public final class UserManagerServiceMockedTest {
        mockUserIsInCall(false);

        assertThat(mUms.getUserLogoutability(USER_ID))
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO);
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_DEVICE_NOT_SUPPORTED);
    }

    @Test
    @EnableFlags(FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_Hsum_SystemUserCannotLogout() throws Exception {
    public void
            testGetUserLogoutability_HsumAndInteractiveHeadlessSystemUser_SystemUserCannotLogout()
                    throws Exception {
        setSystemUserHeadless(true);
        mockCanSwitchToHeadlessSystemUser(true);
        mockCurrentUser(UserHandle.USER_SYSTEM);
        assertThat(mUms.getUserLogoutability(UserHandle.USER_SYSTEM))
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER);
@@ -1016,6 +1019,7 @@ public final class UserManagerServiceMockedTest {
    @EnableFlags(FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_CannotSwitch_CannotLogout() throws Exception {
        setSystemUserHeadless(true);
        mockCanSwitchToHeadlessSystemUser(true);
        addUser(USER_ID);
        addUser(OTHER_USER_ID);
        setLastForegroundTime(OTHER_USER_ID, 1_000_000L);
Loading