Loading core/java/android/os/IUserManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ interface IUserManager { Bundle getApplicationRestrictionsForUser(in String packageName, int userId); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); int removeUserOrSetEphemeral(int userId); boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); Loading core/java/android/os/UserManager.java +17 −0 Original line number Diff line number Diff line Loading @@ -3977,6 +3977,23 @@ public class UserManager { } } /** * Immediately removes the user or, if the user cannot be removed, such as when the user is * the current user, then set the user as ephemeral so that it will be removed when it is * stopped. * * @return the {@link com.android.server.pm.UserManagerService.RemoveResult} code * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int removeUserOrSetEphemeral(@UserIdInt int userId) { try { return mService.removeUserOrSetEphemeral(userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Updates the user's name. * Loading services/core/java/com/android/server/pm/PackageManagerShellCommand.java +47 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; import android.accounts.IAccountManager; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.role.IRoleManager; Loading Loading @@ -2686,9 +2687,18 @@ class PackageManagerShellCommand extends ShellCommand { } } // pm remove-user [--set-ephemeral-if-in-use] USER_ID public int runRemoveUser() throws RemoteException { int userId; String arg = getNextArg(); String arg; boolean setEphemeralIfInUse = false; while ((arg = getNextOption()) != null) { if (arg.equals("--set-ephemeral-if-in-use")) { setEphemeralIfInUse = true; } } arg = getNextArg(); if (arg == null) { getErrPrintWriter().println("Error: no user id specified."); return 1; Loading @@ -2696,6 +2706,15 @@ class PackageManagerShellCommand extends ShellCommand { userId = UserHandle.parseUserArg(arg); IUserManager um = IUserManager.Stub.asInterface( ServiceManager.getService(Context.USER_SERVICE)); if (setEphemeralIfInUse) { return removeUserOrSetEphemeral(um, userId); } else { return removeUser(um, userId); } } private int removeUser(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing user " + userId); if (um.removeUser(userId)) { getOutPrintWriter().println("Success: removed user"); return 0; Loading @@ -2705,6 +2724,27 @@ class PackageManagerShellCommand extends ShellCommand { } } private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use."); int result = um.removeUserOrSetEphemeral(userId); switch (result) { case UserManagerService.REMOVE_RESULT_REMOVED: getOutPrintWriter().printf("Success: user %d removed\n", userId); return 0; case UserManagerService.REMOVE_RESULT_SET_EPHEMERAL: getOutPrintWriter().printf("Success: user %d set as ephemeral\n", userId); return 0; case UserManagerService.REMOVE_RESULT_ALREADY_BEING_REMOVED: getOutPrintWriter().printf("Success: user %d is already being removed\n", userId); return 0; default: getErrPrintWriter().printf("Error: couldn't remove or mark ephemeral user id %d\n", userId); return 1; } } public int runSetUserRestriction() throws RemoteException { int userId = UserHandle.USER_SYSTEM; String opt = getNextOption(); Loading Loading @@ -3769,9 +3809,13 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --restricted is shorthand for '--user-type android.os.usertype.full.RESTRICTED'."); pw.println(" --guest is shorthand for '--user-type android.os.usertype.full.GUEST'."); pw.println(""); pw.println(" remove-user USER_ID"); pw.println(" remove-user [--set-ephemeral-if-in-use] USER_ID"); pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data"); pw.println(" associated with that user"); pw.println(" associated with that user."); pw.println(" --set-ephemeral-if-in-use: If the user is currently running and"); pw.println(" therefore cannot be removed immediately, mark the user as ephemeral"); pw.println(" so that it will be automatically removed when possible (after user"); pw.println(" switch or reboot)"); pw.println(""); pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE"); pw.println(""); Loading services/core/java/com/android/server/pm/UserManagerService.java +123 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.Manifest; import android.annotation.ColorRes; import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; Loading Loading @@ -110,7 +111,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; import com.android.server.am.UserState; import com.android.server.storage.DeviceStorageMonitorInternal; import com.android.server.utils.TimingsTraceAndSlog; Loading @@ -132,6 +132,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -246,6 +248,43 @@ public class UserManagerService extends IUserManager.Stub { static final int WRITE_USER_MSG = 1; static final int WRITE_USER_DELAY = 2*1000; // 2 seconds /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user has been successfully removed. */ public static final int REMOVE_RESULT_REMOVED = 0; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user has had its {@link UserInfo#FLAG_EPHEMERAL} flag set to {@code true}, so that it will be * removed when the user is stopped or on boot. */ public static final int REMOVE_RESULT_SET_EPHEMERAL = 1; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user is already in the process of being removed. */ public static final int REMOVE_RESULT_ALREADY_BEING_REMOVED = 2; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that an error occurred * that prevented the user from being removed or set as ephemeral. */ public static final int REMOVE_RESULT_ERROR = 3; /** * Possible response codes from {@link #removeUserOrSetEphemeral(int)}. */ @IntDef(prefix = { "REMOVE_RESULT_" }, value = { REMOVE_RESULT_REMOVED, REMOVE_RESULT_SET_EPHEMERAL, REMOVE_RESULT_ALREADY_BEING_REMOVED, REMOVE_RESULT_ERROR, }) @Retention(RetentionPolicy.SOURCE) public @interface RemoveResult {} // Tron counters private static final String TRON_GUEST_CREATED = "users_guest_created"; private static final String TRON_USER_CREATED = "users_user_created"; Loading @@ -253,9 +292,17 @@ public class UserManagerService extends IUserManager.Stub { private final Context mContext; private final PackageManagerService mPm; /** * Lock for packages. If using with {@link #mUsersLock}, {@link #mPackagesLock} should be * acquired first. */ private final Object mPackagesLock; private final UserDataPreparer mUserDataPreparer; // Short-term lock for internal state, when interaction/sync with PM is not required /** * Short-term lock for internal state, when interaction/sync with PM is not required. If using * with {@link #mPackagesLock}, {@link #mPackagesLock} should be acquired first. */ private final Object mUsersLock = LockGuard.installNewLock(LockGuard.INDEX_USER); private final Object mRestrictionsLock = new Object(); // Used for serializing access to app restriction files Loading Loading @@ -3862,13 +3909,7 @@ public class UserManagerService extends IUserManager.Stub { Slog.i(LOG_TAG, "removeUser u" + userId); checkManageOrCreateUsersPermission("Only the system can remove users"); final boolean isManagedProfile; synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); isManagedProfile = userInfo != null && userInfo.isManagedProfile(); } String restriction = isManagedProfile ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; Loading @@ -3882,6 +3923,21 @@ public class UserManagerService extends IUserManager.Stub { return removeUserUnchecked(userId); } /** * Returns the string name of the restriction to check for user removal. The restriction name * varies depending on whether the user is a managed profile. */ private String getUserRemovalRestriction(@UserIdInt int userId) { final boolean isManagedProfile; final UserInfo userInfo; synchronized (mUsersLock) { userInfo = getUserInfoLU(userId); } isManagedProfile = userInfo != null && userInfo.isManagedProfile(); return isManagedProfile ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; } private boolean removeUserUnchecked(@UserIdInt int userId) { final long ident = Binder.clearCallingIdentity(); try { Loading Loading @@ -3974,6 +4030,64 @@ public class UserManagerService extends IUserManager.Stub { } } @Override public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) { Slog.i(LOG_TAG, "removeUserOrSetEphemeral u" + userId); checkManageUsersPermission("Only the system can remove users"); final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return REMOVE_RESULT_ERROR; } if (userId == UserHandle.USER_SYSTEM) { Slog.e(LOG_TAG, "System user cannot be removed."); return REMOVE_RESULT_ERROR; } final long ident = Binder.clearCallingIdentity(); try { final UserData userData; synchronized (mPackagesLock) { synchronized (mUsersLock) { userData = mUsers.get(userId); if (userData == null) { Slog.e(LOG_TAG, "Cannot remove user " + userId + ", invalid user id provided."); return REMOVE_RESULT_ERROR; } if (mRemovingUserIds.get(userId)) { Slog.e(LOG_TAG, "User " + userId + " is already scheduled for removal."); return REMOVE_RESULT_ALREADY_BEING_REMOVED; } } // Attempt to immediately remove a non-current user final int currentUser = ActivityManager.getCurrentUser(); if (currentUser != userId) { // Attempt to remove the user. This will fail if the user is the current user if (removeUser(userId)) { return REMOVE_RESULT_REMOVED; } Slog.w(LOG_TAG, "Unable to immediately remove non-current user: " + userId + ". User is still set as ephemeral and will be removed on user " + "switch or reboot."); } // If the user 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. userData.info.flags |= UserInfo.FLAG_EPHEMERAL; writeUserLP(userData); return REMOVE_RESULT_SET_EPHEMERAL; } } finally { Binder.restoreCallingIdentity(ident); } } void finishRemoveUser(final @UserIdInt int userId) { if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userId); Loading services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +106 −20 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Slog; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; Loading @@ -58,6 +59,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.GuardedBy; /** Test {@link UserManager} functionality. */ @RunWith(AndroidJUnit4.class) public final class UserManagerTest { Loading Loading @@ -134,7 +137,7 @@ public final class UserManagerTest { @SmallTest @Test public void testHasSystemUser() throws Exception { assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @MediumTest Loading Loading @@ -164,9 +167,9 @@ public final class UserManagerTest { assertThat(user1).isNotNull(); assertThat(user2).isNotNull(); assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(findUser(user1.id)).isTrue(); assertThat(findUser(user2.id)).isTrue(); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(hasUser(user1.id)).isTrue(); assertThat(hasUser(user2.id)).isTrue(); } @MediumTest Loading @@ -175,7 +178,7 @@ public final class UserManagerTest { UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); removeUser(userInfo.id); assertThat(findUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } @MediumTest Loading @@ -199,7 +202,7 @@ public final class UserManagerTest { } } assertThat(findUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } @MediumTest Loading @@ -208,6 +211,79 @@ public final class UserManagerTest { assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null)); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception { final int currentUser = ActivityManager.getCurrentUser(); final UserInfo user1 = createUser("User 1", /* flags= */ 0); mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true, asHandle(currentUser)); try { assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false, asHandle(currentUser)); } assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isFalse(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception { assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception { assertThat(hasUser(Integer.MAX_VALUE)).isFalse(); assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception { final int startUser = ActivityManager.getCurrentUser(); final UserInfo user1 = createUser("User 1", /* flags= */ 0); // Switch to the user just created. switchUser(user1.id, null, /* ignoreHandle= */ true); assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_SET_EPHEMERAL); assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isTrue(); // Switch back to the starting user. switchUser(startUser, null, /* ignoreHandle= */ true); // User is removed once switch is complete synchronized (mUserRemoveLock) { waitForUserRemovalLocked(user1.id); } assertThat(hasUser(user1.id)).isFalse(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception { final UserInfo user1 = createUser("User 1", /* flags= */ 0); synchronized (mUserRemoveLock) { assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(user1.id); } assertThat(hasUser(user1.id)).isFalse(); } /** Tests creating a FULL user via specifying userType. */ @MediumTest @Test Loading Loading @@ -608,15 +684,20 @@ public final class UserManagerTest { () -> mUserManager.getUserCreationTime(asHandle(user.id))); } private boolean findUser(int id) { @Nullable private UserInfo getUser(int id) { List<UserInfo> list = mUserManager.getUsers(); for (UserInfo user : list) { if (user.id == id) { return true; return user; } } return false; return null; } private boolean hasUser(int id) { return getUser(id) != null; } @MediumTest Loading Loading @@ -918,6 +999,12 @@ public final class UserManagerTest { private void removeUser(int userId) { synchronized (mUserRemoveLock) { mUserManager.removeUser(userId); waitForUserRemovalLocked(userId); } } @GuardedBy("mUserRemoveLock") private void waitForUserRemovalLocked(int userId) { long time = System.currentTimeMillis(); while (mUserManager.getUserInfo(userId) != null) { try { Loading @@ -931,7 +1018,6 @@ public final class UserManagerTest { } } } } private UserInfo createUser(String name, int flags) { UserInfo user = mUserManager.createUser(name, flags); Loading Loading
core/java/android/os/IUserManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -86,6 +86,7 @@ interface IUserManager { Bundle getApplicationRestrictionsForUser(in String packageName, int userId); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); int removeUserOrSetEphemeral(int userId); boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); Loading
core/java/android/os/UserManager.java +17 −0 Original line number Diff line number Diff line Loading @@ -3977,6 +3977,23 @@ public class UserManager { } } /** * Immediately removes the user or, if the user cannot be removed, such as when the user is * the current user, then set the user as ephemeral so that it will be removed when it is * stopped. * * @return the {@link com.android.server.pm.UserManagerService.RemoveResult} code * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int removeUserOrSetEphemeral(@UserIdInt int userId) { try { return mService.removeUserOrSetEphemeral(userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Updates the user's name. * Loading
services/core/java/com/android/server/pm/PackageManagerShellCommand.java +47 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; import android.accounts.IAccountManager; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.role.IRoleManager; Loading Loading @@ -2686,9 +2687,18 @@ class PackageManagerShellCommand extends ShellCommand { } } // pm remove-user [--set-ephemeral-if-in-use] USER_ID public int runRemoveUser() throws RemoteException { int userId; String arg = getNextArg(); String arg; boolean setEphemeralIfInUse = false; while ((arg = getNextOption()) != null) { if (arg.equals("--set-ephemeral-if-in-use")) { setEphemeralIfInUse = true; } } arg = getNextArg(); if (arg == null) { getErrPrintWriter().println("Error: no user id specified."); return 1; Loading @@ -2696,6 +2706,15 @@ class PackageManagerShellCommand extends ShellCommand { userId = UserHandle.parseUserArg(arg); IUserManager um = IUserManager.Stub.asInterface( ServiceManager.getService(Context.USER_SERVICE)); if (setEphemeralIfInUse) { return removeUserOrSetEphemeral(um, userId); } else { return removeUser(um, userId); } } private int removeUser(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing user " + userId); if (um.removeUser(userId)) { getOutPrintWriter().println("Success: removed user"); return 0; Loading @@ -2705,6 +2724,27 @@ class PackageManagerShellCommand extends ShellCommand { } } private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId) throws RemoteException { Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use."); int result = um.removeUserOrSetEphemeral(userId); switch (result) { case UserManagerService.REMOVE_RESULT_REMOVED: getOutPrintWriter().printf("Success: user %d removed\n", userId); return 0; case UserManagerService.REMOVE_RESULT_SET_EPHEMERAL: getOutPrintWriter().printf("Success: user %d set as ephemeral\n", userId); return 0; case UserManagerService.REMOVE_RESULT_ALREADY_BEING_REMOVED: getOutPrintWriter().printf("Success: user %d is already being removed\n", userId); return 0; default: getErrPrintWriter().printf("Error: couldn't remove or mark ephemeral user id %d\n", userId); return 1; } } public int runSetUserRestriction() throws RemoteException { int userId = UserHandle.USER_SYSTEM; String opt = getNextOption(); Loading Loading @@ -3769,9 +3809,13 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --restricted is shorthand for '--user-type android.os.usertype.full.RESTRICTED'."); pw.println(" --guest is shorthand for '--user-type android.os.usertype.full.GUEST'."); pw.println(""); pw.println(" remove-user USER_ID"); pw.println(" remove-user [--set-ephemeral-if-in-use] USER_ID"); pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data"); pw.println(" associated with that user"); pw.println(" associated with that user."); pw.println(" --set-ephemeral-if-in-use: If the user is currently running and"); pw.println(" therefore cannot be removed immediately, mark the user as ephemeral"); pw.println(" so that it will be automatically removed when possible (after user"); pw.println(" switch or reboot)"); pw.println(""); pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE"); pw.println(""); Loading
services/core/java/com/android/server/pm/UserManagerService.java +123 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.Manifest; import android.annotation.ColorRes; import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; Loading Loading @@ -110,7 +111,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; import com.android.server.am.UserState; import com.android.server.storage.DeviceStorageMonitorInternal; import com.android.server.utils.TimingsTraceAndSlog; Loading @@ -132,6 +132,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -246,6 +248,43 @@ public class UserManagerService extends IUserManager.Stub { static final int WRITE_USER_MSG = 1; static final int WRITE_USER_DELAY = 2*1000; // 2 seconds /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user has been successfully removed. */ public static final int REMOVE_RESULT_REMOVED = 0; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user has had its {@link UserInfo#FLAG_EPHEMERAL} flag set to {@code true}, so that it will be * removed when the user is stopped or on boot. */ public static final int REMOVE_RESULT_SET_EPHEMERAL = 1; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified * user is already in the process of being removed. */ public static final int REMOVE_RESULT_ALREADY_BEING_REMOVED = 2; /** * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that an error occurred * that prevented the user from being removed or set as ephemeral. */ public static final int REMOVE_RESULT_ERROR = 3; /** * Possible response codes from {@link #removeUserOrSetEphemeral(int)}. */ @IntDef(prefix = { "REMOVE_RESULT_" }, value = { REMOVE_RESULT_REMOVED, REMOVE_RESULT_SET_EPHEMERAL, REMOVE_RESULT_ALREADY_BEING_REMOVED, REMOVE_RESULT_ERROR, }) @Retention(RetentionPolicy.SOURCE) public @interface RemoveResult {} // Tron counters private static final String TRON_GUEST_CREATED = "users_guest_created"; private static final String TRON_USER_CREATED = "users_user_created"; Loading @@ -253,9 +292,17 @@ public class UserManagerService extends IUserManager.Stub { private final Context mContext; private final PackageManagerService mPm; /** * Lock for packages. If using with {@link #mUsersLock}, {@link #mPackagesLock} should be * acquired first. */ private final Object mPackagesLock; private final UserDataPreparer mUserDataPreparer; // Short-term lock for internal state, when interaction/sync with PM is not required /** * Short-term lock for internal state, when interaction/sync with PM is not required. If using * with {@link #mPackagesLock}, {@link #mPackagesLock} should be acquired first. */ private final Object mUsersLock = LockGuard.installNewLock(LockGuard.INDEX_USER); private final Object mRestrictionsLock = new Object(); // Used for serializing access to app restriction files Loading Loading @@ -3862,13 +3909,7 @@ public class UserManagerService extends IUserManager.Stub { Slog.i(LOG_TAG, "removeUser u" + userId); checkManageOrCreateUsersPermission("Only the system can remove users"); final boolean isManagedProfile; synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); isManagedProfile = userInfo != null && userInfo.isManagedProfile(); } String restriction = isManagedProfile ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; Loading @@ -3882,6 +3923,21 @@ public class UserManagerService extends IUserManager.Stub { return removeUserUnchecked(userId); } /** * Returns the string name of the restriction to check for user removal. The restriction name * varies depending on whether the user is a managed profile. */ private String getUserRemovalRestriction(@UserIdInt int userId) { final boolean isManagedProfile; final UserInfo userInfo; synchronized (mUsersLock) { userInfo = getUserInfoLU(userId); } isManagedProfile = userInfo != null && userInfo.isManagedProfile(); return isManagedProfile ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; } private boolean removeUserUnchecked(@UserIdInt int userId) { final long ident = Binder.clearCallingIdentity(); try { Loading Loading @@ -3974,6 +4030,64 @@ public class UserManagerService extends IUserManager.Stub { } } @Override public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) { Slog.i(LOG_TAG, "removeUserOrSetEphemeral u" + userId); checkManageUsersPermission("Only the system can remove users"); final String restriction = getUserRemovalRestriction(userId); if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return REMOVE_RESULT_ERROR; } if (userId == UserHandle.USER_SYSTEM) { Slog.e(LOG_TAG, "System user cannot be removed."); return REMOVE_RESULT_ERROR; } final long ident = Binder.clearCallingIdentity(); try { final UserData userData; synchronized (mPackagesLock) { synchronized (mUsersLock) { userData = mUsers.get(userId); if (userData == null) { Slog.e(LOG_TAG, "Cannot remove user " + userId + ", invalid user id provided."); return REMOVE_RESULT_ERROR; } if (mRemovingUserIds.get(userId)) { Slog.e(LOG_TAG, "User " + userId + " is already scheduled for removal."); return REMOVE_RESULT_ALREADY_BEING_REMOVED; } } // Attempt to immediately remove a non-current user final int currentUser = ActivityManager.getCurrentUser(); if (currentUser != userId) { // Attempt to remove the user. This will fail if the user is the current user if (removeUser(userId)) { return REMOVE_RESULT_REMOVED; } Slog.w(LOG_TAG, "Unable to immediately remove non-current user: " + userId + ". User is still set as ephemeral and will be removed on user " + "switch or reboot."); } // If the user 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. userData.info.flags |= UserInfo.FLAG_EPHEMERAL; writeUserLP(userData); return REMOVE_RESULT_SET_EPHEMERAL; } } finally { Binder.restoreCallingIdentity(ident); } } void finishRemoveUser(final @UserIdInt int userId) { if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userId); Loading
services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +106 −20 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Slog; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; Loading @@ -58,6 +59,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.GuardedBy; /** Test {@link UserManager} functionality. */ @RunWith(AndroidJUnit4.class) public final class UserManagerTest { Loading Loading @@ -134,7 +137,7 @@ public final class UserManagerTest { @SmallTest @Test public void testHasSystemUser() throws Exception { assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @MediumTest Loading Loading @@ -164,9 +167,9 @@ public final class UserManagerTest { assertThat(user1).isNotNull(); assertThat(user2).isNotNull(); assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(findUser(user1.id)).isTrue(); assertThat(findUser(user2.id)).isTrue(); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); assertThat(hasUser(user1.id)).isTrue(); assertThat(hasUser(user2.id)).isTrue(); } @MediumTest Loading @@ -175,7 +178,7 @@ public final class UserManagerTest { UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); removeUser(userInfo.id); assertThat(findUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } @MediumTest Loading @@ -199,7 +202,7 @@ public final class UserManagerTest { } } assertThat(findUser(userInfo.id)).isFalse(); assertThat(hasUser(userInfo.id)).isFalse(); } @MediumTest Loading @@ -208,6 +211,79 @@ public final class UserManagerTest { assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null)); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception { final int currentUser = ActivityManager.getCurrentUser(); final UserInfo user1 = createUser("User 1", /* flags= */ 0); mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true, asHandle(currentUser)); try { assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false, asHandle(currentUser)); } assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isFalse(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception { assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception { assertThat(hasUser(Integer.MAX_VALUE)).isFalse(); assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE)).isEqualTo( UserManagerService.REMOVE_RESULT_ERROR); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception { final int startUser = ActivityManager.getCurrentUser(); final UserInfo user1 = createUser("User 1", /* flags= */ 0); // Switch to the user just created. switchUser(user1.id, null, /* ignoreHandle= */ true); assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_SET_EPHEMERAL); assertThat(hasUser(user1.id)).isTrue(); assertThat(getUser(user1.id).isEphemeral()).isTrue(); // Switch back to the starting user. switchUser(startUser, null, /* ignoreHandle= */ true); // User is removed once switch is complete synchronized (mUserRemoveLock) { waitForUserRemovalLocked(user1.id); } assertThat(hasUser(user1.id)).isFalse(); } @MediumTest @Test public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception { final UserInfo user1 = createUser("User 1", /* flags= */ 0); synchronized (mUserRemoveLock) { assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo( UserManagerService.REMOVE_RESULT_REMOVED); waitForUserRemovalLocked(user1.id); } assertThat(hasUser(user1.id)).isFalse(); } /** Tests creating a FULL user via specifying userType. */ @MediumTest @Test Loading Loading @@ -608,15 +684,20 @@ public final class UserManagerTest { () -> mUserManager.getUserCreationTime(asHandle(user.id))); } private boolean findUser(int id) { @Nullable private UserInfo getUser(int id) { List<UserInfo> list = mUserManager.getUsers(); for (UserInfo user : list) { if (user.id == id) { return true; return user; } } return false; return null; } private boolean hasUser(int id) { return getUser(id) != null; } @MediumTest Loading Loading @@ -918,6 +999,12 @@ public final class UserManagerTest { private void removeUser(int userId) { synchronized (mUserRemoveLock) { mUserManager.removeUser(userId); waitForUserRemovalLocked(userId); } } @GuardedBy("mUserRemoveLock") private void waitForUserRemovalLocked(int userId) { long time = System.currentTimeMillis(); while (mUserManager.getUserInfo(userId) != null) { try { Loading @@ -931,7 +1018,6 @@ public final class UserManagerTest { } } } } private UserInfo createUser(String name, int flags) { UserInfo user = mUserManager.createUser(name, flags); Loading