Loading services/core/java/com/android/server/pm/UserJourneyLogger.java +3 −1 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class UserJourneyLogger { public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5; public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6; public static final int ERROR_CODE_INVALID_USER_TYPE = 7; public static final int ERROR_CODE_USER_IS_LAST_ADMIN = 8; @IntDef(prefix = {"ERROR_CODE"}, value = { ERROR_CODE_UNSPECIFIED, Loading @@ -78,7 +79,8 @@ public class UserJourneyLogger { ERROR_CODE_USER_ALREADY_AN_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN, ERROR_CODE_INVALID_SESSION_ID, ERROR_CODE_INVALID_USER_TYPE ERROR_CODE_INVALID_USER_TYPE, ERROR_CODE_USER_IS_LAST_ADMIN, }) public @interface UserJourneyErrorCode { } Loading services/core/java/com/android/server/pm/UserManagerService.java +30 −15 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_USER_TYPE; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_LAST_ADMIN; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN; import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_DEMOTE_MAIN_USER; import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_PROMOTE_MAIN_USER; Loading Loading @@ -2337,14 +2338,14 @@ public class UserManagerService extends IUserManager.Stub { currentUserId, userId, /* userType */ "", /* userFlags */ -1); return; } else if (user.info.isAdmin()) { // Exit if the user is already an Admin. // Exit if the user is already an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN); return; } else if (user.info.isProfile() || user.info.isGuest() || user.info.isRestricted()) { // Profiles, guest users or restricted profiles cannot become an Admin. // Profiles, guest users or restricted profiles cannot become an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_INVALID_USER_TYPE); return; Loading Loading @@ -2376,15 +2377,21 @@ public class UserManagerService extends IUserManager.Stub { USER_JOURNEY_REVOKE_ADMIN, currentUserId, userId, "", -1); return; } else if (!user.info.isAdmin()) { // Exit if user is not an Admin. // Exit if user is not an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN); return; } else if ((user.info.flags & UserInfo.FLAG_SYSTEM) != 0) { // System user must always be an Admin. // System user cannot lose its admin status. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_INVALID_USER_TYPE); return; } else if (isNonRemovableLastAdminUserLU(user.info)) { // This is the last admin user and this device requires that it not lose its // admin status. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_LAST_ADMIN); return; } user.info.flags ^= UserInfo.FLAG_ADMIN; writeUserLP(user); Loading Loading @@ -7043,18 +7050,11 @@ public class UserManagerService extends IUserManager.Stub { msg); return UserManager.REMOVE_RESULT_ALREADY_BEING_REMOVED; } if (android.multiuser.Flags.disallowRemovingLastAdminUser()) { if (getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser)) { // For HSUM, the headless system user is currently flagged as an admin user now. // Thus we must exclude it when checking for the last admin user, and only consider // full admin users. b/419105275 will investigate not making HSU an admin. if (isLastFullAdminUserLU(userData.info)) { Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.", userId, msg); if (isNonRemovableLastAdminUserLU(userData.info)) { Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.", userId, msg); return UserManager.REMOVE_RESULT_ERROR_LAST_ADMIN_USER; } } } return UserManager.REMOVE_RESULT_USER_IS_REMOVABLE; } Loading Loading @@ -8841,6 +8841,21 @@ public class UserManagerService extends IUserManager.Stub { return userInfo.isMain() && isMainUserPermanentAdmin(); } /** * Returns true if we must not delete this user or revoke its admin status because it is the * last admin user on a device that requires there to always be at least one admin. */ @GuardedBy("mUsersLock") private boolean isNonRemovableLastAdminUserLU(UserInfo userInfo) { return android.multiuser.Flags.disallowRemovingLastAdminUser() && getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser) // For HSUM, the headless system user is currently flagged as an admin user now. // Thus we must exclude it when checking for the last admin user, and only consider // full admin users. // TODO(b/419105275): Investigate not making HSU an admin. && isLastFullAdminUserLU(userInfo); } /** Returns if the user is the last admin user that is a full user. */ @GuardedBy("mUsersLock") @VisibleForTesting Loading services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java +25 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_ADMIN; import static android.content.pm.UserInfo.FLAG_FULL; import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION; import static android.multiuser.Flags.FLAG_DEMOTE_MAIN_USER; import static android.multiuser.Flags.FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER; import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES; import static android.multiuser.Flags.FLAG_LOGOUT_USER_API; import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE; Loading Loading @@ -236,10 +237,10 @@ public final class UserManagerServiceMockedTest { doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); mockIsLowRamDevice(false); // Called when getting boot user. config_hsumBootStrategy is 0 by default. mSpyResources = spy(mSpiedContext.getResources()); when(mSpiedContext.getResources()).thenReturn(mSpyResources); mockHsumBootStrategy(BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER); mockDisallowRemovingLastAdminUser(false); doReturn(mSpyResources).when(Resources::getSystem); Loading Loading @@ -1822,6 +1823,19 @@ public final class UserManagerServiceMockedTest { assertThat(mUsers.get(UserHandle.USER_SYSTEM).info.isAdmin()).isTrue(); } @Test @RequiresFlagsEnabled(FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER) public void testRevokeUserAdminFailsForLastFullAdmin() { mockDisallowRemovingLastAdminUser(true); // Mark system user as headless so that it is not a full admin user. setSystemUserHeadless(true); addAdminUser(USER_ID); mUms.revokeUserAdmin(USER_ID); assertThat(mUsers.get(USER_ID).info.isAdmin()).isTrue(); } /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. Loading Loading @@ -1958,6 +1972,16 @@ public final class UserManagerServiceMockedTest { .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); } private void mockDisallowRemovingLastAdminUser(boolean disallow) { boolean previousValue = mSpyResources .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser); Log.d(TAG, "mockDisallowRemovingLastAdminUser(): will return " + disallow + " instead of " + previousValue); doReturn(disallow) .when(mSpyResources) .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser); } private void mockUserIsInCall(boolean isInCall) { when(mTelecomManager.isInCall()).thenReturn(isInCall); } Loading Loading
services/core/java/com/android/server/pm/UserJourneyLogger.java +3 −1 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ public class UserJourneyLogger { public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5; public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6; public static final int ERROR_CODE_INVALID_USER_TYPE = 7; public static final int ERROR_CODE_USER_IS_LAST_ADMIN = 8; @IntDef(prefix = {"ERROR_CODE"}, value = { ERROR_CODE_UNSPECIFIED, Loading @@ -78,7 +79,8 @@ public class UserJourneyLogger { ERROR_CODE_USER_ALREADY_AN_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN, ERROR_CODE_INVALID_SESSION_ID, ERROR_CODE_INVALID_USER_TYPE ERROR_CODE_INVALID_USER_TYPE, ERROR_CODE_USER_IS_LAST_ADMIN, }) public @interface UserJourneyErrorCode { } Loading
services/core/java/com/android/server/pm/UserManagerService.java +30 −15 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_USER_TYPE; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_LAST_ADMIN; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN; import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_DEMOTE_MAIN_USER; import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_PROMOTE_MAIN_USER; Loading Loading @@ -2337,14 +2338,14 @@ public class UserManagerService extends IUserManager.Stub { currentUserId, userId, /* userType */ "", /* userFlags */ -1); return; } else if (user.info.isAdmin()) { // Exit if the user is already an Admin. // Exit if the user is already an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN); return; } else if (user.info.isProfile() || user.info.isGuest() || user.info.isRestricted()) { // Profiles, guest users or restricted profiles cannot become an Admin. // Profiles, guest users or restricted profiles cannot become an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_INVALID_USER_TYPE); return; Loading Loading @@ -2376,15 +2377,21 @@ public class UserManagerService extends IUserManager.Stub { USER_JOURNEY_REVOKE_ADMIN, currentUserId, userId, "", -1); return; } else if (!user.info.isAdmin()) { // Exit if user is not an Admin. // Exit if user is not an admin. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN); return; } else if ((user.info.flags & UserInfo.FLAG_SYSTEM) != 0) { // System user must always be an Admin. // System user cannot lose its admin status. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_INVALID_USER_TYPE); return; } else if (isNonRemovableLastAdminUserLU(user.info)) { // This is the last admin user and this device requires that it not lose its // admin status. mUserJourneyLogger.logUserJourneyFinishWithError(currentUserId, user.info, USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_LAST_ADMIN); return; } user.info.flags ^= UserInfo.FLAG_ADMIN; writeUserLP(user); Loading Loading @@ -7043,18 +7050,11 @@ public class UserManagerService extends IUserManager.Stub { msg); return UserManager.REMOVE_RESULT_ALREADY_BEING_REMOVED; } if (android.multiuser.Flags.disallowRemovingLastAdminUser()) { if (getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser)) { // For HSUM, the headless system user is currently flagged as an admin user now. // Thus we must exclude it when checking for the last admin user, and only consider // full admin users. b/419105275 will investigate not making HSU an admin. if (isLastFullAdminUserLU(userData.info)) { Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.", userId, msg); if (isNonRemovableLastAdminUserLU(userData.info)) { Slogf.e(LOG_TAG, "User %d can not be %s, last admin user cannot be removed.", userId, msg); return UserManager.REMOVE_RESULT_ERROR_LAST_ADMIN_USER; } } } return UserManager.REMOVE_RESULT_USER_IS_REMOVABLE; } Loading Loading @@ -8841,6 +8841,21 @@ public class UserManagerService extends IUserManager.Stub { return userInfo.isMain() && isMainUserPermanentAdmin(); } /** * Returns true if we must not delete this user or revoke its admin status because it is the * last admin user on a device that requires there to always be at least one admin. */ @GuardedBy("mUsersLock") private boolean isNonRemovableLastAdminUserLU(UserInfo userInfo) { return android.multiuser.Flags.disallowRemovingLastAdminUser() && getContextResources().getBoolean(R.bool.config_disallowRemovingLastAdminUser) // For HSUM, the headless system user is currently flagged as an admin user now. // Thus we must exclude it when checking for the last admin user, and only consider // full admin users. // TODO(b/419105275): Investigate not making HSU an admin. && isLastFullAdminUserLU(userInfo); } /** Returns if the user is the last admin user that is a full user. */ @GuardedBy("mUsersLock") @VisibleForTesting Loading
services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java +25 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_ADMIN; import static android.content.pm.UserInfo.FLAG_FULL; import static android.multiuser.Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION; import static android.multiuser.Flags.FLAG_DEMOTE_MAIN_USER; import static android.multiuser.Flags.FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER; import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES; import static android.multiuser.Flags.FLAG_LOGOUT_USER_API; import static android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE; Loading Loading @@ -236,10 +237,10 @@ public final class UserManagerServiceMockedTest { doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); mockIsLowRamDevice(false); // Called when getting boot user. config_hsumBootStrategy is 0 by default. mSpyResources = spy(mSpiedContext.getResources()); when(mSpiedContext.getResources()).thenReturn(mSpyResources); mockHsumBootStrategy(BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER); mockDisallowRemovingLastAdminUser(false); doReturn(mSpyResources).when(Resources::getSystem); Loading Loading @@ -1822,6 +1823,19 @@ public final class UserManagerServiceMockedTest { assertThat(mUsers.get(UserHandle.USER_SYSTEM).info.isAdmin()).isTrue(); } @Test @RequiresFlagsEnabled(FLAG_DISALLOW_REMOVING_LAST_ADMIN_USER) public void testRevokeUserAdminFailsForLastFullAdmin() { mockDisallowRemovingLastAdminUser(true); // Mark system user as headless so that it is not a full admin user. setSystemUserHeadless(true); addAdminUser(USER_ID); mUms.revokeUserAdmin(USER_ID); assertThat(mUsers.get(USER_ID).info.isAdmin()).isTrue(); } /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. Loading Loading @@ -1958,6 +1972,16 @@ public final class UserManagerServiceMockedTest { .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); } private void mockDisallowRemovingLastAdminUser(boolean disallow) { boolean previousValue = mSpyResources .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser); Log.d(TAG, "mockDisallowRemovingLastAdminUser(): will return " + disallow + " instead of " + previousValue); doReturn(disallow) .when(mSpyResources) .getBoolean(com.android.internal.R.bool.config_disallowRemovingLastAdminUser); } private void mockUserIsInCall(boolean isInCall) { when(mTelecomManager.isInCall()).thenReturn(isInCall); } Loading