diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 2b1b32ed3df29..bd9c0822e70c2 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -16,12 +16,17 @@ package android.content.pm; +import android.annotation.IntDef; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.util.DebugUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Per-user information. @@ -94,6 +99,25 @@ public class UserInfo implements Parcelable { */ public static final int FLAG_DEMO = 0x00000200; + /** + * @hide + */ + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_PRIMARY, + FLAG_ADMIN, + FLAG_GUEST, + FLAG_RESTRICTED, + FLAG_INITIALIZED, + FLAG_MANAGED_PROFILE, + FLAG_DISABLED, + FLAG_QUIET_MODE, + FLAG_EPHEMERAL, + FLAG_DEMO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserInfoFlag { + } + public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL; @UnsupportedAppUsage @@ -128,6 +152,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. + * + *

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). + * + *

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); @@ -155,6 +191,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; } @@ -165,6 +208,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; } @@ -252,6 +302,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; @@ -268,6 +319,22 @@ public class UserInfo implements Parcelable { return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; } + /** @hide */ + public String toFullString() { + return "UserInfo[id=" + id + + ", name=" + name + + ", flags=" + flagsToString(flags) + + (preCreated ? " (pre-created)" : "") + + (partial ? " (partial)" : "") + + "]"; + } + + /** @hide */ + public static String flagsToString(int flags) { + return DebugUtils.flagsToString(UserInfo.class, "FLAG_", flags); + } + + @Override public int describeContents() { return 0; } @@ -281,9 +348,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); } @@ -308,10 +376,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 63641e538b8e4..ed76077e5c798 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 9c9829fb08e2c..eb1927ebd83a2 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; @@ -2003,18 +2004,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. + * + *

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); @@ -2030,6 +2033,36 @@ public class UserManager { return user; } + /** + * Pre-creates a user with the specified name and options. For non-admin users, default user + * restrictions will be applied. + * + *

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. + * + *

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 diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 6a10d247a5c6c..598a68e90aa8f 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -134,12 +134,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; @@ -369,16 +369,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 @@ -439,8 +441,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; } @@ -556,6 +557,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); @@ -808,7 +820,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); } @@ -1065,6 +1078,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) { mInjector.getWindowManager().startFreezingScreen( R.anim.screen_user_exit, R.anim.screen_user_enter); @@ -1159,13 +1177,11 @@ 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)); } 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, @@ -1174,6 +1190,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); @@ -2131,13 +2151,13 @@ 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(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 @@ -2146,7 +2166,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 81de8e2632998..52a3714cfb38f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2257,6 +2257,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()); @@ -2270,6 +2271,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; @@ -2293,7 +2296,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); } @@ -3169,8 +3172,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 02abd7a447da3..ed60aa54212f4 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; @@ -66,6 +67,7 @@ import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.EnforcingUser; @@ -83,6 +85,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; +import android.util.TimingsTraceLog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -155,6 +158,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"; @@ -582,7 +586,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; @@ -647,18 +652,23 @@ public class UserManagerService extends IUserManager.Stub { @Override public @NonNull List getUsers(boolean excludeDying) { + return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); + } + + private @NonNull List getUsers(boolean excludePartial, boolean excludeDying, + boolean excludePreCreated) { checkManageOrCreateUsersPermission("query users"); synchronized (mUsersLock) { ArrayList users = new ArrayList(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; } @@ -1179,8 +1189,9 @@ public class UserManagerService extends IUserManager.Stub { } } - private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) { - int callingUserId = UserHandle.getCallingUserId(); + private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId, + String name) { + final int callingUserId = UserHandle.getCallingUserId(); if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) || hasManageUsersPermission()) { return; @@ -1193,8 +1204,8 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public boolean isDemoUser(int userId) { - int callingUserId = UserHandle.getCallingUserId(); + public boolean isDemoUser(@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 a demo user"); @@ -1205,6 +1216,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) { @@ -1859,7 +1883,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++; } } @@ -2320,6 +2344,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"); } @@ -2476,6 +2503,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; @@ -2520,6 +2548,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; @@ -2573,6 +2605,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; @@ -2644,7 +2677,8 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, int userId, String[] disallowedPackages) { checkManageOrCreateUsersPermission(flags); - return createUserInternalUnchecked(name, flags, userId, disallowedPackages); + return createUserInternalUnchecked(name, flags, userId, /* preCreate= */ false, + disallowedPackages); } @Override @@ -2659,12 +2693,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; @@ -2672,19 +2719,64 @@ 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(String name, int flags, int parentId, - String[] disallowedPackages) { + private UserInfo createUserInternalUnchecked(@Nullable String name, @UserInfoFlag int flags, + @UserIdInt int parentId, boolean preCreate, + @Nullable String[] disallowedPackages) { + final TimingsTraceLog t = new TimingsTraceLog(LOG_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + t.traceBegin("createUser-" + flags); + try { + return createUserInternalUncheckedNoTracing(name, flags, parentId, preCreate, + disallowedPackages, t); + } finally { + t.traceEnd(); + } + } + + private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, + @UserInfoFlag int flags, @UserIdInt int parentId, boolean preCreate, + @Nullable String[] disallowedPackages, @NonNull TimingsTraceLog 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()) { Log.w(LOG_TAG, "Cannot add user. Not enough space on disk."); 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(); @@ -2705,8 +2797,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; } @@ -2747,8 +2839,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. @@ -2759,9 +2850,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); @@ -2808,18 +2899,97 @@ public class UserManagerService extends IUserManager.Stub { mBaseUserRestrictions.append(userId, restrictions); } mPm.onNewUserCreated(userId); - 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; } + 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. + * + *

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)); + } + 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(); @@ -3651,7 +3821,7 @@ public class UserManagerService extends IUserManager.Stub { try { switch(cmd) { case "list": - return runList(pw); + return runList(pw, shell); default: return shell.handleDefaultCommands(cmd); } @@ -3661,17 +3831,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 users = getUsers(false); + final List 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; } @@ -3708,6 +3919,9 @@ public class UserManagerService extends IUserManager.Stub { if (userInfo.partial) { pw.print(" "); } + if (userInfo.preCreated) { + pw.print(" "); + } pw.println(); pw.print(" State: "); final int state; @@ -3788,11 +4002,12 @@ 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(" User version: " + mUserVersion); } private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) { @@ -3977,7 +4192,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); @@ -4142,7 +4357,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 231025c61c8a9..a7c943e0a2ac7 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; @@ -107,6 +109,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; @@ -128,11 +134,11 @@ public class UserControllerTest { private static final Set 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 START_BACKGROUND_USER_MESSAGE_CODES = newHashSet( - SYSTEM_USER_START_MSG, + USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); @Before @@ -149,7 +155,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); }); } @@ -190,6 +197,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 expectedActions, Set expectedMessageCodes) { assertEquals(expectedActions, getActions(mInjector.mSentIntents)); @@ -469,9 +501,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 getActions(List intents) {