diff options
7 files changed, 422 insertions, 91 deletions
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index df652f190d04..0a99142a9fbd 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -187,6 +187,18 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public boolean guestToRemove; + /** + * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a + * number of users at the first boot, so the actual creation later is faster. + * + * <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular + * user operations (other than user creation per se). + * + * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to + * {@code false}. + */ + public boolean preCreated; + @UnsupportedAppUsage public UserInfo(int id, String name, int flags) { this(id, name, null, flags); @@ -214,6 +226,13 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public boolean isGuest() { + return isGuest(flags); + } + + /** + * Checks if the flag denotes a guest user. + */ + public static boolean isGuest(@UserInfoFlag int flags) { return (flags & FLAG_GUEST) == FLAG_GUEST; } @@ -224,6 +243,13 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public boolean isManagedProfile() { + return isManagedProfile(flags); + } + + /** + * Checks if the flag denotes a managed profile. + */ + public static boolean isManagedProfile(@UserInfoFlag int flags) { return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE; } @@ -315,6 +341,7 @@ public class UserInfo implements Parcelable { lastLoggedInTime = orig.lastLoggedInTime; lastLoggedInFingerprint = orig.lastLoggedInFingerprint; partial = orig.partial; + preCreated = orig.preCreated; profileGroupId = orig.profileGroupId; restrictedProfileParentId = orig.restrictedProfileParentId; guestToRemove = orig.guestToRemove; @@ -339,6 +366,8 @@ public class UserInfo implements Parcelable { return "UserInfo[id=" + id + ", name=" + name + ", flags=" + flagsToString(flags) + + (preCreated ? " (pre-created)" : "") + + (partial ? " (partial)" : "") + "]"; } @@ -362,9 +391,10 @@ public class UserInfo implements Parcelable { dest.writeLong(creationTime); dest.writeLong(lastLoggedInTime); dest.writeString(lastLoggedInFingerprint); - dest.writeInt(partial ? 1 : 0); + dest.writeBoolean(partial); + dest.writeBoolean(preCreated); dest.writeInt(profileGroupId); - dest.writeInt(guestToRemove ? 1 : 0); + dest.writeBoolean(guestToRemove); dest.writeInt(restrictedProfileParentId); dest.writeInt(profileBadge); } @@ -389,10 +419,11 @@ public class UserInfo implements Parcelable { creationTime = source.readLong(); lastLoggedInTime = source.readLong(); lastLoggedInFingerprint = source.readString(); - partial = source.readInt() != 0; + partial = source.readBoolean(); profileGroupId = source.readInt(); - guestToRemove = source.readInt() != 0; + guestToRemove = source.readBoolean(); restrictedProfileParentId = source.readInt(); profileBadge = source.readInt(); + preCreated = source.readBoolean(); } } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 63641e538b8e..ed76077e5c79 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -41,6 +41,7 @@ interface IUserManager { */ UserInfo createUser(in String name, int flags); + UserInfo preCreateUser(int flags); UserInfo createProfileForUser(in String name, int flags, int userHandle, in String[] disallowedPackages); UserInfo createRestrictedProfile(String name, int parentUserHandle); @@ -92,6 +93,7 @@ interface IUserManager { boolean someUserHasSeedAccount(in String accountName, in String accountType); boolean isManagedProfile(int userId); boolean isDemoUser(int userId); + boolean isPreCreated(int userId); UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle, in String[] disallowedPackages); boolean isUserUnlockingOrUnlocked(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index af574da084f0..7bb1cdb2fa16 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -37,6 +37,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.UserInfo; +import android.content.pm.UserInfo.UserInfoFlag; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -2013,18 +2014,20 @@ public class UserManager { /** * Creates a user with the specified name and options. For non-admin users, default user - * restrictions are going to be applied. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * restrictions will be applied. + * + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @param name the user's name - * @param flags flags that identify the type of user and other properties. + * @param flags UserInfo flags that identify the type of user and other properties. * @see UserInfo * - * @return the UserInfo object for the created user, or null if the user could not be created. + * @return the UserInfo object for the created user, or {@code null} if the user could not be + * created. * @hide */ @UnsupportedAppUsage - public UserInfo createUser(String name, int flags) { + public @Nullable UserInfo createUser(@Nullable String name, @UserInfoFlag int flags) { UserInfo user = null; try { user = mService.createUser(name, flags); @@ -2041,6 +2044,36 @@ public class UserManager { } /** + * Pre-creates a user with the specified name and options. For non-admin users, default user + * restrictions will be applied. + * + * <p>This method can be used by OEMs to "warm" up the user creation by pre-creating some users + * at the first boot, so they when the "real" user is created (for example, + * by {@link #createUser(String, int)} or {@link #createGuest(Context, String)}), it takes + * less time. + * + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * @param flags UserInfo flags that identify the type of user and other properties. + * @see UserInfo + * + * @return the UserInfo object for the created user, or {@code null} if the user could not be + * created. + * + * @throw {@link IllegalArgumentException} if {@code flags} contains + * {@link UserInfo#FLAG_MANAGED_PROFILE}. + * + * @hide + */ + public @Nullable UserInfo preCreateUser(@UserInfoFlag int flags) { + try { + return mService.preCreateUser(flags); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Creates a guest user and configures it. * @param context an application context * @param name the name to set for the user diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 5c8e530faf70..8e4474c462ab 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -133,12 +133,12 @@ class UserController implements Handler.Callback { static final int CONTINUE_USER_SWITCH_MSG = 20; static final int USER_SWITCH_TIMEOUT_MSG = 30; static final int START_PROFILES_MSG = 40; - static final int SYSTEM_USER_START_MSG = 50; - static final int SYSTEM_USER_CURRENT_MSG = 60; + static final int USER_START_MSG = 50; + static final int USER_CURRENT_MSG = 60; static final int FOREGROUND_PROFILE_CHANGED_MSG = 70; static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80; static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90; - static final int SYSTEM_USER_UNLOCK_MSG = 100; + static final int USER_UNLOCK_MSG = 100; static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110; static final int START_USER_SWITCH_FG_MSG = 120; @@ -368,16 +368,18 @@ class UserController implements Handler.Callback { } } - mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG, - userId, 0)); - Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT - | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - mInjector.broadcastIntent(intent, null, resultTo, 0, null, null, - new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, - AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, - Binder.getCallingUid(), Binder.getCallingPid(), userId); + if (!mInjector.getUserManager().isPreCreated(userId)) { + mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG, + userId, 0)); + Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mInjector.broadcastIntent(intent, null, resultTo, 0, null, null, + new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, + AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, + Binder.getCallingUid(), Binder.getCallingPid(), userId); + } } // We need to delay unlocking managed profiles until the parent user @@ -438,8 +440,7 @@ class UserController implements Handler.Callback { // Dispatch unlocked to system services; when fully dispatched, // that calls through to the next "unlocked" phase - mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss) - .sendToTarget(); + mHandler.obtainMessage(USER_UNLOCK_MSG, userId, 0, uss).sendToTarget(); }); return true; } @@ -555,6 +556,17 @@ class UserController implements Handler.Callback { } } + if (userInfo.preCreated) { + Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString()); + // Pre-created user was started right after creation so services could properly + // intialize it; it should be stopped right away as it's not really a "real" user. + // TODO(b/140750212): in the long-term, we should add a onCreateUser() callback + // on SystemService instead. + stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null, + /* keyEvictedCallback= */ null); + return; + } + // Spin up app widgets prior to boot-complete, so they can be ready promptly mInjector.startUserWidgets(userId); @@ -799,7 +811,8 @@ class UserController implements Handler.Callback { mInjector.systemServiceManagerCleanupUser(userId); mInjector.stackSupervisorRemoveUser(userId); // Remove the user if it is ephemeral. - if (getUserInfo(userId).isEphemeral()) { + UserInfo userInfo = getUserInfo(userId); + if (userInfo.isEphemeral() && !userInfo.preCreated) { mInjector.getUserManager().removeUserEvenWhenDisallowed(userId); } @@ -1069,6 +1082,11 @@ class UserController implements Handler.Callback { return false; } + if (foreground && userInfo.preCreated) { + Slog.w(TAG, "Cannot start pre-created user #" + userId + " as foreground"); + return false; + } + if (foreground && mUserSwitchUiEnabled) { t.traceBegin("startFreezingScreen"); mInjector.getWindowManager().startFreezingScreen( @@ -1178,15 +1196,13 @@ class UserController implements Handler.Callback { // Booting up a new user, need to tell system services about it. // Note that this is on the same handler as scheduling of broadcasts, // which is important because it needs to go first. - mHandler.sendMessage( - mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0)); + mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0)); t.traceEnd(); } t.traceBegin("sendMessages"); if (foreground) { - mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId, - oldUserId)); + mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId)); mHandler.removeMessages(REPORT_USER_SWITCH_MSG); mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG, @@ -1195,6 +1211,10 @@ class UserController implements Handler.Callback { oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS); } + if (userInfo.preCreated) { + needStart = false; + } + if (needStart) { // Send USER_STARTED broadcast Intent intent = new Intent(Intent.ACTION_USER_STARTED); @@ -2168,14 +2188,14 @@ class UserController implements Handler.Callback { case START_PROFILES_MSG: startProfiles(); break; - case SYSTEM_USER_START_MSG: + case USER_START_MSG: mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, Integer.toString(msg.arg1), msg.arg1); mInjector.getSystemServiceManager().startUser(TimingsTraceAndSlog.newAsyncLog(), msg.arg1); break; - case SYSTEM_USER_UNLOCK_MSG: + case USER_UNLOCK_MSG: final int userId = msg.arg1; mInjector.getSystemServiceManager().unlockUser(userId); // Loads recents on a worker thread that allows disk I/O @@ -2184,7 +2204,7 @@ class UserController implements Handler.Callback { }); finishUserUnlocked((UserState) msg.obj); break; - case SYSTEM_USER_CURRENT_MSG: + case USER_CURRENT_MSG: mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH, Integer.toString(msg.arg2), msg.arg2); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 6435d9b410a9..1c1c947c0a9e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2415,6 +2415,7 @@ class PackageManagerShellCommand extends ShellCommand { int userId = -1; int flags = 0; String opt; + boolean preCreateOnly = false; while ((opt = getNextOption()) != null) { if ("--profileOf".equals(opt)) { userId = UserHandle.parseUserArg(getNextArgRequired()); @@ -2428,6 +2429,8 @@ class PackageManagerShellCommand extends ShellCommand { flags |= UserInfo.FLAG_GUEST; } else if ("--demo".equals(opt)) { flags |= UserInfo.FLAG_DEMO; + } else if ("--pre-create-only".equals(opt)) { + preCreateOnly = true; } else { getErrPrintWriter().println("Error: unknown option " + opt); return 1; @@ -2451,7 +2454,7 @@ class PackageManagerShellCommand extends ShellCommand { accm.addSharedAccountsFromParentUser(parentUserId, userId, (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); } else if (userId < 0) { - info = um.createUser(name, flags); + info = preCreateOnly ? um.preCreateUser(flags) : um.createUser(name, flags); } else { info = um.createProfileForUser(name, flags, userId, null); } @@ -3364,8 +3367,11 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]"); pw.println(" Trim cache files to reach the given free space."); pw.println(""); + pw.println(" list users"); + pw.println(" Lists the current users."); + pw.println(""); pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]"); - pw.println(" [--guest] USER_NAME"); + pw.println(" [--guest] [--pre-create-only] USER_NAME"); pw.println(" Create a new user with the given USER_NAME, printing the new user identifier"); pw.println(" of the user."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index dace598a790b..ca273babcefb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -41,6 +41,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; +import android.content.pm.UserInfo.UserInfoFlag; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.Binder; @@ -158,6 +159,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_SERIAL_NO = "serialNumber"; private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; private static final String ATTR_PARTIAL = "partial"; + private static final String ATTR_PRE_CREATED = "preCreated"; private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove"; private static final String ATTR_USER_VERSION = "version"; private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId"; @@ -597,7 +599,8 @@ public class UserManagerService extends IUserManager.Stub { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i).info; - if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) { + if ((ui.partial || ui.guestToRemove || (ui.isEphemeral() && !ui.preCreated)) + && i != 0) { partials.add(ui); addRemovingUserIdLocked(ui.id); ui.partial = true; @@ -662,18 +665,23 @@ public class UserManagerService extends IUserManager.Stub { @Override public @NonNull List<UserInfo> getUsers(boolean excludeDying) { + return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); + } + + private @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, + boolean excludePreCreated) { checkManageOrCreateUsersPermission("query users"); synchronized (mUsersLock) { ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i).info; - if (ui.partial) { + if ((excludePartial && ui.partial) + || (excludeDying && mRemovingUserIds.get(ui.id)) + || (excludePreCreated && ui.preCreated)) { continue; } - if (!excludeDying || !mRemovingUserIds.get(ui.id)) { - users.add(userWithName(ui)); - } + users.add(userWithName(ui)); } return users; } @@ -1196,7 +1204,7 @@ public class UserManagerService extends IUserManager.Stub { private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId, String name) { - int callingUserId = UserHandle.getCallingUserId(); + final int callingUserId = UserHandle.getCallingUserId(); if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) || hasManageUsersPermission()) { return; @@ -1210,7 +1218,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean isDemoUser(@UserIdInt int userId) { - int callingUserId = UserHandle.getCallingUserId(); + final int callingUserId = UserHandle.getCallingUserId(); if (callingUserId != userId && !hasManageUsersPermission()) { throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId + " is a demo user"); @@ -1222,6 +1230,19 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isPreCreated(@UserIdInt int userId) { + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != userId && !hasManageUsersPermission()) { + throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId + + " is pre-created"); + } + synchronized (mUsersLock) { + UserInfo userInfo = getUserInfoLU(userId); + return userInfo != null && userInfo.preCreated; + } + } + + @Override public boolean isRestricted() { synchronized (mUsersLock) { return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted(); @@ -1871,7 +1892,7 @@ public class UserManagerService extends IUserManager.Stub { // Skip over users being removed for (int i = 0; i < totalUserCount; i++) { UserInfo user = mUsers.valueAt(i).info; - if (!mRemovingUserIds.get(user.id) && !user.isGuest()) { + if (!mRemovingUserIds.get(user.id) && !user.isGuest() && !user.preCreated) { aliveUserCount++; } } @@ -2362,6 +2383,9 @@ public class UserManagerService extends IUserManager.Stub { if (userInfo.partial) { serializer.attribute(null, ATTR_PARTIAL, "true"); } + if (userInfo.preCreated) { + serializer.attribute(null, ATTR_PRE_CREATED, "true"); + } if (userInfo.guestToRemove) { serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true"); } @@ -2518,6 +2542,7 @@ public class UserManagerService extends IUserManager.Stub { int profileBadge = 0; int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID; boolean partial = false; + boolean preCreated = false; boolean guestToRemove = false; boolean persistSeedData = false; String seedAccountName = null; @@ -2562,6 +2587,10 @@ public class UserManagerService extends IUserManager.Stub { if ("true".equals(valueString)) { partial = true; } + valueString = parser.getAttributeValue(null, ATTR_PRE_CREATED); + if ("true".equals(valueString)) { + preCreated = true; + } valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE); if ("true".equals(valueString)) { guestToRemove = true; @@ -2615,6 +2644,7 @@ public class UserManagerService extends IUserManager.Stub { userInfo.lastLoggedInTime = lastLoggedInTime; userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint; userInfo.partial = partial; + userInfo.preCreated = preCreated; userInfo.guestToRemove = guestToRemove; userInfo.profileGroupId = profileGroupId; userInfo.profileBadge = profileBadge; @@ -2686,7 +2716,8 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, @UserIdInt int userId, String[] disallowedPackages) { checkManageOrCreateUsersPermission(flags); - return createUserInternalUnchecked(name, flags, userId, disallowedPackages); + return createUserInternalUnchecked(name, flags, userId, /* preCreate= */ false, + disallowedPackages); } @Override @@ -2701,12 +2732,25 @@ public class UserManagerService extends IUserManager.Stub { return createUserInternal(name, flags, UserHandle.USER_NULL); } - private UserInfo createUserInternal(String name, int flags, int parentId) { + @Override + public UserInfo preCreateUser(int flags) { + checkManageOrCreateUsersPermission(flags); + + Preconditions.checkArgument(!UserInfo.isManagedProfile(flags), + "cannot pre-create managed profiles"); + + return createUserInternalUnchecked(/* name= */ null, flags, + /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true, + /* disallowedPackages= */ null); + } + + private UserInfo createUserInternal(@Nullable String name, @UserInfoFlag int flags, + @UserIdInt int parentId) { return createUserInternal(name, flags, parentId, null); } - private UserInfo createUserInternal(String name, int flags, int parentId, - String[] disallowedPackages) { + private UserInfo createUserInternal(@Nullable String name, @UserInfoFlag int flags, + @UserIdInt int parentId, @Nullable String[] disallowedPackages) { String restriction = ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0) ? UserManager.DISALLOW_ADD_MANAGED_PROFILE : UserManager.DISALLOW_ADD_USER; @@ -2714,21 +2758,56 @@ public class UserManagerService extends IUserManager.Stub { Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled."); return null; } - return createUserInternalUnchecked(name, flags, parentId, disallowedPackages); + return createUserInternalUnchecked(name, flags, parentId, /* preCreate= */ false, + disallowedPackages); } - private UserInfo createUserInternalUnchecked(@Nullable String name, int flags, - int parentId, @Nullable String[] disallowedPackages) { - TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - t.traceBegin("createUser"); - UserInfo userInfo = - createUserInternalUncheckedNoTracing(name, flags, parentId, disallowedPackages, t); - t.traceEnd(); - return userInfo; + private UserInfo createUserInternalUnchecked(@Nullable String name, @UserInfoFlag int flags, + @UserIdInt int parentId, boolean preCreate, + @Nullable String[] disallowedPackages) { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("createUser-" + flags); + try { + return createUserInternalUncheckedNoTracing(name, flags, parentId, preCreate, + disallowedPackages, t); + } finally { + t.traceEnd(); + } } - private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, int flags, - int parentId, @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) { + private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, + @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, + @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) { + + // First try to use a pre-created user (if available). + // NOTE: currently we don't support pre-created managed profiles + if (!preCreate && (parentId < 0 && !UserInfo.isManagedProfile(flags))) { + final UserData preCreatedUserData; + synchronized (mUsersLock) { + preCreatedUserData = getPreCreatedUserLU(flags); + } + if (preCreatedUserData != null) { + final UserInfo preCreatedUser = preCreatedUserData.info; + Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " for flags + " + + UserInfo.flagsToString(flags)); + if (DBG) { + Log.d(LOG_TAG, "pre-created user flags: " + + UserInfo.flagsToString(preCreatedUser.flags) + + " new-user flags: " + UserInfo.flagsToString(flags)); + } + preCreatedUser.name = name; + preCreatedUser.preCreated = false; + preCreatedUser.creationTime = getCreationTime(); + + dispatchUserAddedIntent(preCreatedUser); + + writeUserLP(preCreatedUserData); + writeUserListLP(); + + return preCreatedUser; + } + } + DeviceStorageMonitorInternal dsm = LocalServices .getService(DeviceStorageMonitorInternal.class); if (dsm.isMemoryLow()) { @@ -2736,8 +2815,8 @@ public class UserManagerService extends IUserManager.Stub { return null; } - final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0; - final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0; + final boolean isGuest = UserInfo.isGuest(flags); + final boolean isManagedProfile = UserInfo.isManagedProfile(flags); final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0; final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0; final long ident = Binder.clearCallingIdentity(); @@ -2758,8 +2837,8 @@ public class UserManagerService extends IUserManager.Stub { return null; } if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) { - // If we're not adding a guest/demo user or a managed profile and the limit has - // been reached, cannot add a user. + // If we're not adding a guest/demo user or a managed profile, + // and the limit has been reached, cannot add a user. Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached."); return null; } @@ -2794,8 +2873,7 @@ public class UserManagerService extends IUserManager.Stub { userId = getNextAvailableId(); Environment.getUserSystemDirectory(userId).mkdirs(); - boolean ephemeralGuests = Resources.getSystem() - .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral); + boolean ephemeralGuests = areGuestUsersEphemeral(); synchronized (mUsersLock) { // Add ephemeral flag to guests/users if required. Also inherit it from parent. @@ -2806,9 +2884,9 @@ public class UserManagerService extends IUserManager.Stub { userInfo = new UserInfo(userId, name, null, flags); userInfo.serialNumber = mNextSerialNumber++; - long now = System.currentTimeMillis(); - userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0; + userInfo.creationTime = getCreationTime(); userInfo.partial = true; + userInfo.preCreated = preCreate; userInfo.lastLoggedInFingerprint = Build.FINGERPRINT; if (isManagedProfile && parentId != UserHandle.USER_NULL) { userInfo.profileBadge = getFreeProfileBadgeLU(parentId); @@ -2869,17 +2947,40 @@ public class UserManagerService extends IUserManager.Stub { t.traceBegin("PM.onNewUserCreated"); mPm.onNewUserCreated(userId); - t.traceEnd(); - Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); - addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, - android.Manifest.permission.MANAGE_USERS); - MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED - : (isDemo ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1); + if (preCreate) { + // Must start user (which will be stopped right away, through + // UserController.finishUserUnlockedCompleted) so services can properly + // intialize it. + // TODO(b/140750212): in the long-term, we should add a onCreateUser() callback + // on SystemService instead. + Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString()); + final IActivityManager am = ActivityManager.getService(); + try { + am.startUserInBackground(userId); + } catch (RemoteException e) { + Slog.w(LOG_TAG, "could not start pre-created user " + userId, e); + } + } else { + dispatchUserAddedIntent(userInfo); + } + } finally { Binder.restoreCallingIdentity(ident); } + + // TODO(b/140750212): it's possible to reach "max users overflow" when the user is created + // "from scratch" (i.e., not from a pre-created user) and reaches the maximum number of + // users without counting the pre-created one. Then when the pre-created is converted, the + // "effective" number of max users is exceeds. Example: + // Max: 3 Current: 2 full (u0 and u10) + 1 pre-created (u11) + // Step 1: create(/* flags doesn't match u11 */): u12 is created, "effective max" is now 3 + // (u0, u10, u12) but "real" max is 4 (u0, u10, u11, u12) + // Step 2: create(/* flags match u11 */): u11 is converted, now "effective max" is also 4 + // (u0, u10, u11, u12) + // One way to avoid this issue is by removing a pre-created user from the pool when the + // "real" max exceeds the max here. + return userInfo; } @@ -2888,6 +2989,62 @@ public class UserManagerService extends IUserManager.Stub { return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade); } + private long getCreationTime() { + final long now = System.currentTimeMillis(); + return (now > EPOCH_PLUS_30_YEARS) ? now : 0; + } + + private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) { + Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); + mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, + android.Manifest.permission.MANAGE_USERS); + MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED + : (userInfo.isDemo() ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1); + } + + private boolean areGuestUsersEphemeral() { + return Resources.getSystem() + .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral); + } + + /** + * Gets a pre-created user for the given flag. + * + * <p>Should be used only during user creation, so the pre-created user can be used (instead of + * creating and initializing a new user from scratch). + */ + // TODO(b/140750212): add unit test + @GuardedBy("mUsersLock") + private @Nullable UserData getPreCreatedUserLU(@UserInfoFlag int flags) { + if (DBG) { + Slog.d(LOG_TAG, "getPreCreatedUser(): initialFlags= " + UserInfo.flagsToString(flags)); + } + flags |= UserInfo.FLAG_FULL; + if (UserInfo.isGuest(flags) && areGuestUsersEphemeral()) { + flags |= UserInfo.FLAG_EPHEMERAL; + } + if (DBG) { + Slog.d(LOG_TAG, "getPreCreatedUser(): targetFlags= " + UserInfo.flagsToString(flags)); + } + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData user = mUsers.valueAt(i); + if (DBG) Slog.d(LOG_TAG, i + ":" + user.info.toFullString()); + if (user.info.preCreated + && (user.info.flags & ~UserInfo.FLAG_INITIALIZED) == flags) { + if (!user.info.isInitialized()) { + Slog.w(LOG_TAG, "found pre-created user for flags " + + "" + UserInfo.flagsToString(flags) + + ", but it's not initialized yet: " + user.info.toFullString()); + continue; + } + return user; + } + } + return null; + } + @VisibleForTesting UserData putUserInfo(UserInfo userInfo) { final UserData userData = new UserData(); @@ -3728,7 +3885,7 @@ public class UserManagerService extends IUserManager.Stub { try { switch(cmd) { case "list": - return runList(pw); + return runList(pw, shell); default: return shell.handleDefaultCommands(cmd); } @@ -3738,17 +3895,58 @@ public class UserManagerService extends IUserManager.Stub { return -1; } - private int runList(PrintWriter pw) throws RemoteException { + private int runList(PrintWriter pw, Shell shell) throws RemoteException { + boolean all = false; + boolean verbose = false; + String opt; + while ((opt = shell.getNextOption()) != null) { + switch (opt) { + case "-v": + verbose = true; + break; + case "--all": + all = true; + break; + default: + pw.println("Invalid option: " + opt); + return -1; + } + } final IActivityManager am = ActivityManager.getService(); - final List<UserInfo> users = getUsers(false); + final List<UserInfo> users = getUsers(/* excludePartial= */ !all, + /* excludingDying=*/ false, /* excludePreCreated= */ !all); if (users == null) { pw.println("Error: couldn't get users"); return 1; } else { - pw.println("Users:"); - for (int i = 0; i < users.size(); i++) { - String running = am.isUserRunning(users.get(i).id, 0) ? " running" : ""; - pw.println("\t" + users.get(i).toString() + running); + final int size = users.size(); + int currentUser = UserHandle.USER_NULL; + if (verbose) { + pw.printf("%d users:\n\n", size); + currentUser = am.getCurrentUser().id; + } else { + // NOTE: the standard "list users" command is used by integration tests and + // hence should not be changed. If you need to add more info, use the + // verbose option. + pw.println("Users:"); + } + for (int i = 0; i < size; i++) { + final UserInfo user = users.get(i); + final boolean running = am.isUserRunning(user.id, 0); + final boolean current = user.id == currentUser; + if (verbose) { + pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s\n", i, user.id, user.name, + UserInfo.flagsToString(user.flags), + running ? " (running)" : "", + user.partial ? " (partial)" : "", + user.preCreated ? " (pre-created)" : "", + current ? " (current)" : ""); + } else { + // NOTE: the standard "list users" command is used by integration tests and + // hence should not be changed. If you need to add more info, use the + // verbose option. + pw.printf("\t%s%s\n", user, running ? " running" : ""); + } } return 0; } @@ -3785,6 +3983,9 @@ public class UserManagerService extends IUserManager.Stub { if (userInfo.partial) { pw.print(" <partial>"); } + if (userInfo.preCreated) { + pw.print(" <pre-created>"); + } pw.println(); pw.print(" Flags: "); pw.print(userInfo.flags); pw.print(" ("); pw.print(UserInfo.flagsToString(userInfo.flags)); pw.println(")"); @@ -3867,10 +4068,10 @@ public class UserManagerService extends IUserManager.Stub { // Dump some capabilities pw.println(); - pw.println(" Max users: " + UserManager.getMaxSupportedUsers()); + pw.print(" Max users: " + UserManager.getMaxSupportedUsers()); + pw.println(" (limit reached: " + isUserLimitReached() + ")"); pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers()); - pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean( - com.android.internal.R.bool.config_guestUserEphemeral)); + pw.println(" All guests ephemeral: " + areGuestUsersEphemeral()); pw.println(" Is split-system user: " + UserManager.isSplitSystemUser()); pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode()); pw.println(" User version: " + mUserVersion); @@ -4076,7 +4277,7 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createUserEvenWhenDisallowed(String name, int flags, String[] disallowedPackages) { UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL, - disallowedPackages); + /* preCreated= */ false, disallowedPackages); // Keep this in sync with UserManager.createUser if (user != null && !user.isAdmin() && !user.isDemo()) { setUserRestriction(UserManager.DISALLOW_SMS, true, user.id); @@ -4270,7 +4471,7 @@ public class UserManagerService extends IUserManager.Stub { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" list"); + pw.println(" list [-v] [-all]"); pw.println(" Prints all users on the system."); } } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index d4b7e7e913f6..79cc3db90fff 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -25,8 +25,8 @@ import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; -import static com.android.server.am.UserController.SYSTEM_USER_CURRENT_MSG; -import static com.android.server.am.UserController.SYSTEM_USER_START_MSG; +import static com.android.server.am.UserController.USER_CURRENT_MSG; +import static com.android.server.am.UserController.USER_START_MSG; import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG; import static com.google.android.collect.Lists.newArrayList; @@ -53,11 +53,13 @@ import static org.mockito.Mockito.validateMockitoUsage; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.UserIdInt; import android.app.IUserSwitchObserver; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.UserInfo; +import android.content.pm.UserInfo.UserInfoFlag; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -106,6 +108,10 @@ public class UserControllerTest { private static final int TEST_USER_ID1 = 101; private static final int TEST_USER_ID2 = 102; private static final int NONEXIST_USER_ID = 2; + private static final int TEST_PRE_CREATED_USER_ID = 103; + + private static final int NO_USERINFO_FLAGS = 0; + private static final String TAG = UserControllerTest.class.getSimpleName(); private static final long HANDLER_WAIT_TIME_MS = 100; @@ -127,11 +133,11 @@ public class UserControllerTest { private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet( REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, - SYSTEM_USER_START_MSG, - SYSTEM_USER_CURRENT_MSG); + USER_START_MSG, + USER_CURRENT_MSG); private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet( - SYSTEM_USER_START_MSG, + USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); @Before @@ -148,7 +154,8 @@ public class UserControllerTest { doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt()); mUserController = new UserController(mInjector); - setUpUser(TEST_USER_ID, 0); + setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); + setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true); }); } @@ -188,6 +195,31 @@ public class UserControllerTest { startForegroundUserAssertions(); } + @Test + public void testStartPreCreatedUser_foreground() { + assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true)); + } + + @Test + public void testStartPreCreatedUser_background() throws Exception { + assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false)); + + verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); + verify(mInjector, never()).clearAllLockedTasks(anyString()); + + assertWithMessage("should not have received intents") + .that(getActions(mInjector.mSentIntents)).isEmpty(); + // TODO(b/140868593): should have received a USER_UNLOCK_MSG message as well, but it doesn't + // because StorageManager.isUserKeyUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to + // properly fix it, we'd need to move this class to FrameworksMockingServicesTests so we can + // mock static methods (but moving this class would involve changing the presubmit tests, + // and the cascade effect goes on...). In fact, a better approach would to not assert the + // binder calls, but their side effects (in this case, that the user is stopped right away) + assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes()) + .containsExactly(USER_START_MSG); + } + private void startUserAssertions( List<String> expectedActions, Set<Integer> expectedMessageCodes) { assertEquals(expectedActions, getActions(mInjector.mSentIntents)); @@ -467,9 +499,15 @@ public class UserControllerTest { continueUserSwitchAssertions(newUserId, expectOldUserStopping); } - private void setUpUser(int userId, int flags) { + private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) { + setUpUser(userId, flags, /* preCreated= */ false); + } + + private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated) { UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); + userInfo.preCreated = preCreated; when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); + when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated); } private static List<String> getActions(List<Intent> intents) { |