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

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

Merge "Add getUserLogoutability" into main

parents ad7facfb 027ccfbc
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -615,3 +615,10 @@ flag {
    bug: "346553745"
    is_exported: true
}

flag {
     namespace: "multi_user"
     name: "logout_user_api"
     description: "Add API to logout user"
     bug: "350045389"
}
+2 −0
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@ interface IUserManager {
    long getUserCreationTime(int userId);
    int getUserSwitchability(int userId);
    boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USERS)")
    int getUserLogoutability(int userId);
    boolean isRestricted(int userId);
    boolean canHaveRestrictedProfile(int userId);
    boolean canAddPrivateProfile(int userId);
+68 −0
Original line number Diff line number Diff line
@@ -2260,6 +2260,45 @@ public class UserManager {
    })
    public @interface UserSwitchabilityResult {}

    /**
     * Indicates that user can logout.
     * @hide
     */
    public static final int LOGOUTABILITY_STATUS_OK = 0;

    /**
     * Indicates that user cannot logout because it is the system user.
     * @hide
     */
    public static final int LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER = 1;

    /**
     * Indicates that user cannot logout because there is no suitable user to logout to. This is
     * generally applicable to Headless System User Mode devices that do not have an interactive
     * system user.
     * @hide
     */
    public static final int LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO = 2;

    /**
     * Indicates that user cannot logout because user switch cannot happen.
     * @hide
     */
    public static final int LOGOUTABILITY_STATUS_CANNOT_SWITCH = 3;

    /**
     * Result returned in {@link #getUserLogoutability()} indicating user logoutability.
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = false, prefix = { "LOGOUTABILITY_STATUS_" }, value = {
            LOGOUTABILITY_STATUS_OK,
            LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER,
            LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO,
            LOGOUTABILITY_STATUS_CANNOT_SWITCH
    })
    public @interface UserLogoutability {}

    /**
     * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
     * the specified user has been successfully removed.
@@ -2736,6 +2775,35 @@ public class UserManager {
        }
    }

    /**
     * Returns whether logging out is currently allowed for the context user.
     *
     * <p>Logging out is not allowed in the following cases:
     * <ol>
     * <li>the user is system user
     * <li>there is no suitable user to logout to (if no interactive system user)
     * <li>the user is in a phone call
     * <li>{@link #DISALLOW_USER_SWITCH} is set
     * <li>system user hasn't been unlocked yet
     * </ol>
     *
     * @return A {@link UserLogoutability} flag indicating if the user can logout,
     * one of {@link #LOGOUTABILITY_STATUS_OK},
     * {@link #LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER},
     * {@link #LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO},
     * {@link #LOGOUTABILITY_STATUS_CANNOT_SWITCH}.
     * @hide
     */
    @UserHandleAware
    @RequiresPermission(Manifest.permission.MANAGE_USERS)
    public @UserLogoutability int getUserLogoutability() {
        try {
            return mService.getUserLogoutability(mUserId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the userId for the context user.
     *
+50 −2
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
import android.os.UserManager.QuietModeFlag;
import android.os.UserManager.UserLogoutability;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
@@ -1472,10 +1473,13 @@ public class UserManagerService extends IUserManager.Stub {
       return UserHandle.USER_NULL;
   }


    @Override
    public @CanBeNULL @UserIdInt int getPreviousFullUserToEnterForeground() {
        checkQueryOrCreateUsersPermission("get previous user");
        return getPreviousFullUserToEnterForegroundUnchecked();
    }

    private int getPreviousFullUserToEnterForegroundUnchecked() {
        int previousUser = UserHandle.USER_NULL;
        long latestEnteredTime = 0;
        final int currentUser = getCurrentUserId();
@@ -2915,7 +2919,8 @@ public class UserManagerService extends IUserManager.Stub {
     * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is
     * switchable.
     */
    public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) {
    @Override
    public @UserManager.UserSwitchabilityResult int getUserSwitchability(@UserIdInt int userId) {
        if (Flags.getUserSwitchabilityPermission()) {
            if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
                throw new SecurityException(
@@ -2993,6 +2998,49 @@ public class UserManagerService extends IUserManager.Stub {
                && multiUserSettingOn;
    }

    @Override
    public @UserLogoutability int getUserLogoutability(@UserIdInt int userId) {
        if (!android.multiuser.Flags.logoutUserApi()) {
            throw new UnsupportedOperationException(
                    "aconfig flag android.multiuser.logout_user_api not enabled");
        }

        checkManageUsersPermission("getUserLogoutability");

        if (userId == UserHandle.USER_SYSTEM) {
            return UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER;
        }

        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 (getUserSwitchability(userId) != UserManager.SWITCHABILITY_STATUS_OK) {
            return UserManager.LOGOUTABILITY_STATUS_CANNOT_SWITCH;
        }

        if (getUserToLogoutCurrentUserTo() == UserHandle.USER_NULL) {
            return UserManager.LOGOUTABILITY_STATUS_NO_SUITABLE_USER_TO_LOGOUT_TO;
        }

        return UserManager.LOGOUTABILITY_STATUS_OK;
    }

    /**
     * Returns the user to switch to, when logging out current user. If in HSUM and has interactive
     * system user, then logout would switch to the system user. Otherwise, logout would switch to
     * the previous foreground user.
     */
    private @UserIdInt int getUserToLogoutCurrentUserTo() {
        if (isHeadlessSystemUserMode() && canSwitchToHeadlessSystemUser()) {
            return USER_SYSTEM;
        }
        return getPreviousFullUserToEnterForegroundUnchecked();
    }

    @Override
    public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable,
            @UserIdInt int userId) {
+86 −3
Original line number Diff line number Diff line
@@ -61,9 +61,12 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -166,6 +169,7 @@ public final class UserManagerServiceTest {
    private @Mock PackageManagerInternal mPackageManagerInternal;
    private @Mock KeyguardManager mKeyguardManager;
    private @Mock PowerManager mPowerManager;
    private @Mock TelecomManager mTelecomManager;

    /**
     * Reference to the {@link UserManagerService} being tested.
@@ -192,6 +196,7 @@ public final class UserManagerServiceTest {
        when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
        doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
        when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
        when(mSpiedContext.getSystemService(TelecomManager.class)).thenReturn(mTelecomManager);
        mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
        mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
        doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -885,9 +890,7 @@ public final class UserManagerServiceTest {
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
        // Even if the headless system user switchable flag is true, the boot user should be the
        // first switchable full user.
        doReturn(true)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
        mockCanSwitchToHeadlessSystemUser(true);

        assertThat(mUms.getBootUser()).isEqualTo(USER_ID);
    }
@@ -906,6 +909,75 @@ public final class UserManagerServiceTest {
                () -> mUms.getBootUser());
    }

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_HsumAndInteractiveHeadlessSystem_UserCanLogout()
            throws Exception {
        setSystemUserHeadless(true);
        addUser(USER_ID);
        setLastForegroundTime(USER_ID, 1_000_000L);
        mockCurrentUser(USER_ID);

        mockCanSwitchToHeadlessSystemUser(true);
        mockUserIsInCall(false);

        assertThat(mUms.getUserLogoutability(USER_ID))
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_OK);
    }

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_HsumAndNonInteractiveHeadlessSystem_UserCannotLogout()
            throws Exception {
        setSystemUserHeadless(true);
        mockCanSwitchToHeadlessSystemUser(false);
        addUser(USER_ID);
        setLastForegroundTime(USER_ID, 1_000_000L);
        mockCurrentUser(USER_ID);
        mockUserIsInCall(false);

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

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_Hsum_SystemUserCannotLogout() throws Exception {
        setSystemUserHeadless(true);
        mockCurrentUser(UserHandle.USER_SYSTEM);
        assertThat(mUms.getUserLogoutability(UserHandle.USER_SYSTEM))
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER);
    }

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_NonHsum_SystemUserCannotLogout() throws Exception {
        setSystemUserHeadless(false);
        mockCurrentUser(UserHandle.USER_SYSTEM);
        assertThat(
                mUms.getUserLogoutability(UserHandle.USER_SYSTEM)).isEqualTo(
                UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER);
    }

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_CannotSwitch_CannotLogout() throws Exception {
        setSystemUserHeadless(true);
        addUser(USER_ID);
        addUser(OTHER_USER_ID);
        setLastForegroundTime(OTHER_USER_ID, 1_000_000L);
        mockCurrentUser(USER_ID);
        mUms.setUserRestriction(DISALLOW_USER_SWITCH, true, USER_ID);
        assertThat(mUms.getUserLogoutability(USER_ID))
                .isEqualTo(UserManager.LOGOUTABILITY_STATUS_CANNOT_SWITCH);
    }

    @Test
    @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    public void testGetUserLogoutability_LogoutDisabled() throws Exception {
        assertThrows(UnsupportedOperationException.class, () -> mUms.getUserLogoutability(USER_ID));
    }

    /**
     * Returns true if the user's XML file has Default restrictions
     * @param userId Id of the user.
@@ -1021,6 +1093,16 @@ public final class UserManagerServiceTest {
        doReturn(service).when(() -> LocalServices.getService(serviceClass));
    }

    private void mockCanSwitchToHeadlessSystemUser(boolean canSwitch) {
        doReturn(canSwitch)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
    }

    private void mockUserIsInCall(boolean isInCall) {
        when(mTelecomManager.isInCall()).thenReturn(isInCall);
    }

    private void addDefaultProfileAndParent() {
        addUser(PARENT_USER_ID);
        addProfile(PROFILE_USER_ID, PARENT_USER_ID);
@@ -1063,6 +1145,7 @@ public final class UserManagerServiceTest {
    private void addUserData(TestUserData userData) {
        Log.d(TAG, "Adding " + userData);
        mUsers.put(userData.info.id, userData);
        mUms.putUserInfo(userData.info);
    }

    private void setSystemUserHeadless(boolean headless) {