Loading core/java/android/os/UserManager.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -4993,7 +4993,7 @@ public class UserManager { } } /** /** * Removes a user and all associated data. * Removes a user and its profiles along with their associated data. * @param userId the integer handle of the user. * @param userId the integer handle of the user. * @hide * @hide */ */ Loading @@ -5009,7 +5009,7 @@ public class UserManager { } } /** /** * Removes a user and all associated data. * Removes a user and its profiles along with their associated data. * * * @param user the user that needs to be removed. * @param user the user that needs to be removed. * @return {@code true} if the user was successfully removed, {@code false} otherwise. * @return {@code true} if the user was successfully removed, {@code false} otherwise. Loading Loading @@ -5046,9 +5046,9 @@ public class UserManager { } } /** /** * Immediately removes the user or, if the user cannot be removed, such as when the user is * Immediately removes the user and its profiles or, if the user cannot be removed, such as * the current user, then set the user as ephemeral so that it will be removed when it is * when the user is the current user, then set the user as ephemeral * stopped. * so that it will be removed when it is stopped. * * * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction Loading services/core/java/com/android/server/pm/UserManagerService.java +47 −7 Original line number Original line Diff line number Diff line Loading @@ -5199,8 +5199,9 @@ public class UserManagerService extends IUserManager.Stub { } } /** /** * Removes a user and all data directories created for that user. This method should be called * Removes a user and its profiles along with all data directories created for that user * after the user's processes have been terminated. * and its profile. * This method should be called after the user's processes have been terminated. * @param userId the user's id * @param userId the user's id */ */ @Override @Override Loading @@ -5213,13 +5214,52 @@ public class UserManagerService extends IUserManager.Stub { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; return false; } } return removeUserWithProfilesUnchecked(userId); } private boolean removeUserWithProfilesUnchecked(@UserIdInt int userId) { UserInfo userInfo = getUserInfoNoChecks(userId); if (userInfo == null) { Slog.e(LOG_TAG, TextUtils.formatSimple( "Cannot remove user %d, invalid user id provided.", userId)); return false; } if (!userInfo.isProfile()) { int[] profileIds = getProfileIds(userId, false); for (int profileId : profileIds) { if (profileId == userId) { //Remove the associated profiles first and then remove the user continue; } Slog.i(LOG_TAG, "removing profile:" + profileId + "associated with user:" + userId); if (!removeUserUnchecked(profileId)) { // If the profile was not immediately removed, make sure it is marked as // ephemeral. Don't mark as disabled since, per UserInfo.FLAG_DISABLED // documentation, an ephemeral user should only be marked as disabled // when its removal is in progress. Slog.i(LOG_TAG, "Unable to immediately remove profile " + profileId + "associated with user " + userId + ". User is set as ephemeral " + "and will be removed on user switch or reboot."); synchronized (mPackagesLock) { UserData profileData = getUserDataNoChecks(userId); profileData.info.flags |= UserInfo.FLAG_EPHEMERAL; writeUserLP(profileData); } } } } return removeUserUnchecked(userId); return removeUserUnchecked(userId); } } @Override @Override public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { checkCreateUsersPermission("Only the system can remove users"); checkCreateUsersPermission("Only the system can remove users"); return removeUserUnchecked(userId); return removeUserWithProfilesUnchecked(userId); } } /** /** Loading Loading @@ -5255,13 +5295,13 @@ public class UserManagerService extends IUserManager.Stub { } } if (userData == null) { if (userData == null) { Slog.e(LOG_TAG, String.format( Slog.e(LOG_TAG, TextUtils.formatSimple( "Cannot remove user %d, invalid user id provided.", userId)); "Cannot remove user %d, invalid user id provided.", userId)); return false; return false; } } if (mRemovingUserIds.get(userId)) { if (mRemovingUserIds.get(userId)) { Slog.e(LOG_TAG, String.format( Slog.e(LOG_TAG, TextUtils.formatSimple( "User %d is already scheduled for removal.", userId)); "User %d is already scheduled for removal.", userId)); return false; return false; } } Loading Loading @@ -5372,7 +5412,7 @@ public class UserManagerService extends IUserManager.Stub { final int currentUser = getCurrentUserId(); final int currentUser = getCurrentUserId(); if (currentUser != userId) { if (currentUser != userId) { // Attempt to remove the user. This will fail if the user is the current user // Attempt to remove the user. This will fail if the user is the current user if (removeUserUnchecked(userId)) { if (removeUserWithProfilesUnchecked(userId)) { return UserManager.REMOVE_RESULT_REMOVED; return UserManager.REMOVE_RESULT_REMOVED; } } } } Loading Loading @@ -6618,7 +6658,7 @@ public class UserManagerService extends IUserManager.Stub { @Override @Override public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { return removeUserUnchecked(userId); return removeUserWithProfilesUnchecked(userId); } } @Override @Override Loading services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +40 −17 Original line number Original line Diff line number Diff line Loading @@ -284,22 +284,8 @@ public final class UserManagerTest { @Test @Test public void testRemoveUserByHandle() { public void testRemoveUserByHandle() { UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); final UserHandle user = userInfo.getUserHandle(); synchronized (mUserRemoveLock) { removeUser(userInfo.getUserHandle()); mUserManager.removeUser(user); long time = System.currentTimeMillis(); while (mUserManager.getUserInfo(user.getIdentifier()) != null) { try { mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return; } if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) { fail("Timeout waiting for removeUser. userId = " + user.getIdentifier()); } } } assertThat(hasUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } } Loading @@ -307,7 +293,7 @@ public final class UserManagerTest { @MediumTest @MediumTest @Test @Test public void testRemoveUserByHandle_ThrowsException() { public void testRemoveUserByHandle_ThrowsException() { assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null)); assertThrows(IllegalArgumentException.class, () -> removeUser(null)); } } @MediumTest @MediumTest Loading Loading @@ -410,6 +396,30 @@ public final class UserManagerTest { assertThat(hasUser(user1.id)).isFalse(); assertThat(hasUser(user1.id)).isFalse(); } } @MediumTest @Test public void testRemoveUserWhenPossible_withProfiles() throws Exception { assumeHeadlessModeEnabled(); final UserInfo parentUser = createUser("Human User", /* flags= */ 0); final UserInfo cloneProfileUser = createProfileForUser("Clone Profile user", UserManager.USER_TYPE_PROFILE_CLONE, parentUser.id); final UserInfo workProfileUser = createProfileForUser("Work Profile user", UserManager.USER_TYPE_PROFILE_MANAGED, parentUser.id); synchronized (mUserRemoveLock) { assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(), /* overrideDevicePolicy= */ false)) .isEqualTo(UserManager.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(parentUser.id); } assertThat(hasUser(parentUser.id)).isFalse(); assertThat(hasUser(cloneProfileUser.id)).isFalse(); assertThat(hasUser(workProfileUser.id)).isFalse(); } /** Tests creating a FULL user via specifying userType. */ /** Tests creating a FULL user via specifying userType. */ @MediumTest @MediumTest @Test @Test Loading Loading @@ -1182,6 +1192,13 @@ public final class UserManagerTest { } } } } private void removeUser(UserHandle user) { synchronized (mUserRemoveLock) { mUserManager.removeUser(user); waitForUserRemovalLocked(user.getIdentifier()); } } private void removeUser(int userId) { private void removeUser(int userId) { synchronized (mUserRemoveLock) { synchronized (mUserRemoveLock) { mUserManager.removeUser(userId); mUserManager.removeUser(userId); Loading Loading @@ -1261,6 +1278,12 @@ public final class UserManagerTest { && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode())); && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode())); } } private void assumeHeadlessModeEnabled() { // assume headless mode is enabled assumeTrue("Device doesn't have headless mode enabled", UserManager.isHeadlessSystemUserMode()); } private boolean isAutomotive() { private boolean isAutomotive() { return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } } Loading Loading
core/java/android/os/UserManager.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -4993,7 +4993,7 @@ public class UserManager { } } /** /** * Removes a user and all associated data. * Removes a user and its profiles along with their associated data. * @param userId the integer handle of the user. * @param userId the integer handle of the user. * @hide * @hide */ */ Loading @@ -5009,7 +5009,7 @@ public class UserManager { } } /** /** * Removes a user and all associated data. * Removes a user and its profiles along with their associated data. * * * @param user the user that needs to be removed. * @param user the user that needs to be removed. * @return {@code true} if the user was successfully removed, {@code false} otherwise. * @return {@code true} if the user was successfully removed, {@code false} otherwise. Loading Loading @@ -5046,9 +5046,9 @@ public class UserManager { } } /** /** * Immediately removes the user or, if the user cannot be removed, such as when the user is * Immediately removes the user and its profiles or, if the user cannot be removed, such as * the current user, then set the user as ephemeral so that it will be removed when it is * when the user is the current user, then set the user as ephemeral * stopped. * so that it will be removed when it is stopped. * * * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction Loading
services/core/java/com/android/server/pm/UserManagerService.java +47 −7 Original line number Original line Diff line number Diff line Loading @@ -5199,8 +5199,9 @@ public class UserManagerService extends IUserManager.Stub { } } /** /** * Removes a user and all data directories created for that user. This method should be called * Removes a user and its profiles along with all data directories created for that user * after the user's processes have been terminated. * and its profile. * This method should be called after the user's processes have been terminated. * @param userId the user's id * @param userId the user's id */ */ @Override @Override Loading @@ -5213,13 +5214,52 @@ public class UserManagerService extends IUserManager.Stub { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; return false; } } return removeUserWithProfilesUnchecked(userId); } private boolean removeUserWithProfilesUnchecked(@UserIdInt int userId) { UserInfo userInfo = getUserInfoNoChecks(userId); if (userInfo == null) { Slog.e(LOG_TAG, TextUtils.formatSimple( "Cannot remove user %d, invalid user id provided.", userId)); return false; } if (!userInfo.isProfile()) { int[] profileIds = getProfileIds(userId, false); for (int profileId : profileIds) { if (profileId == userId) { //Remove the associated profiles first and then remove the user continue; } Slog.i(LOG_TAG, "removing profile:" + profileId + "associated with user:" + userId); if (!removeUserUnchecked(profileId)) { // If the profile was not immediately removed, make sure it is marked as // ephemeral. Don't mark as disabled since, per UserInfo.FLAG_DISABLED // documentation, an ephemeral user should only be marked as disabled // when its removal is in progress. Slog.i(LOG_TAG, "Unable to immediately remove profile " + profileId + "associated with user " + userId + ". User is set as ephemeral " + "and will be removed on user switch or reboot."); synchronized (mPackagesLock) { UserData profileData = getUserDataNoChecks(userId); profileData.info.flags |= UserInfo.FLAG_EPHEMERAL; writeUserLP(profileData); } } } } return removeUserUnchecked(userId); return removeUserUnchecked(userId); } } @Override @Override public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { checkCreateUsersPermission("Only the system can remove users"); checkCreateUsersPermission("Only the system can remove users"); return removeUserUnchecked(userId); return removeUserWithProfilesUnchecked(userId); } } /** /** Loading Loading @@ -5255,13 +5295,13 @@ public class UserManagerService extends IUserManager.Stub { } } if (userData == null) { if (userData == null) { Slog.e(LOG_TAG, String.format( Slog.e(LOG_TAG, TextUtils.formatSimple( "Cannot remove user %d, invalid user id provided.", userId)); "Cannot remove user %d, invalid user id provided.", userId)); return false; return false; } } if (mRemovingUserIds.get(userId)) { if (mRemovingUserIds.get(userId)) { Slog.e(LOG_TAG, String.format( Slog.e(LOG_TAG, TextUtils.formatSimple( "User %d is already scheduled for removal.", userId)); "User %d is already scheduled for removal.", userId)); return false; return false; } } Loading Loading @@ -5372,7 +5412,7 @@ public class UserManagerService extends IUserManager.Stub { final int currentUser = getCurrentUserId(); final int currentUser = getCurrentUserId(); if (currentUser != userId) { if (currentUser != userId) { // Attempt to remove the user. This will fail if the user is the current user // Attempt to remove the user. This will fail if the user is the current user if (removeUserUnchecked(userId)) { if (removeUserWithProfilesUnchecked(userId)) { return UserManager.REMOVE_RESULT_REMOVED; return UserManager.REMOVE_RESULT_REMOVED; } } } } Loading Loading @@ -6618,7 +6658,7 @@ public class UserManagerService extends IUserManager.Stub { @Override @Override public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { return removeUserUnchecked(userId); return removeUserWithProfilesUnchecked(userId); } } @Override @Override Loading
services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +40 −17 Original line number Original line Diff line number Diff line Loading @@ -284,22 +284,8 @@ public final class UserManagerTest { @Test @Test public void testRemoveUserByHandle() { public void testRemoveUserByHandle() { UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); final UserHandle user = userInfo.getUserHandle(); synchronized (mUserRemoveLock) { removeUser(userInfo.getUserHandle()); mUserManager.removeUser(user); long time = System.currentTimeMillis(); while (mUserManager.getUserInfo(user.getIdentifier()) != null) { try { mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return; } if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) { fail("Timeout waiting for removeUser. userId = " + user.getIdentifier()); } } } assertThat(hasUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } } Loading @@ -307,7 +293,7 @@ public final class UserManagerTest { @MediumTest @MediumTest @Test @Test public void testRemoveUserByHandle_ThrowsException() { public void testRemoveUserByHandle_ThrowsException() { assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null)); assertThrows(IllegalArgumentException.class, () -> removeUser(null)); } } @MediumTest @MediumTest Loading Loading @@ -410,6 +396,30 @@ public final class UserManagerTest { assertThat(hasUser(user1.id)).isFalse(); assertThat(hasUser(user1.id)).isFalse(); } } @MediumTest @Test public void testRemoveUserWhenPossible_withProfiles() throws Exception { assumeHeadlessModeEnabled(); final UserInfo parentUser = createUser("Human User", /* flags= */ 0); final UserInfo cloneProfileUser = createProfileForUser("Clone Profile user", UserManager.USER_TYPE_PROFILE_CLONE, parentUser.id); final UserInfo workProfileUser = createProfileForUser("Work Profile user", UserManager.USER_TYPE_PROFILE_MANAGED, parentUser.id); synchronized (mUserRemoveLock) { assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(), /* overrideDevicePolicy= */ false)) .isEqualTo(UserManager.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(parentUser.id); } assertThat(hasUser(parentUser.id)).isFalse(); assertThat(hasUser(cloneProfileUser.id)).isFalse(); assertThat(hasUser(workProfileUser.id)).isFalse(); } /** Tests creating a FULL user via specifying userType. */ /** Tests creating a FULL user via specifying userType. */ @MediumTest @MediumTest @Test @Test Loading Loading @@ -1182,6 +1192,13 @@ public final class UserManagerTest { } } } } private void removeUser(UserHandle user) { synchronized (mUserRemoveLock) { mUserManager.removeUser(user); waitForUserRemovalLocked(user.getIdentifier()); } } private void removeUser(int userId) { private void removeUser(int userId) { synchronized (mUserRemoveLock) { synchronized (mUserRemoveLock) { mUserManager.removeUser(userId); mUserManager.removeUser(userId); Loading Loading @@ -1261,6 +1278,12 @@ public final class UserManagerTest { && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode())); && (!isAutomotive() || !UserManager.isHeadlessSystemUserMode())); } } private void assumeHeadlessModeEnabled() { // assume headless mode is enabled assumeTrue("Device doesn't have headless mode enabled", UserManager.isHeadlessSystemUserMode()); } private boolean isAutomotive() { private boolean isAutomotive() { return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } } Loading