diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index df652f190d048..0a99142a9fbd0 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. + * + *
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); @@ -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 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 af574da084f02..7bb1cdb2fa16f 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. + * + *
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); @@ -2040,6 +2043,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 5c8e530faf701..8e4474c462ab4 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 6435d9b410a95..1c1c947c0a9e0 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 dace598a790b0..ca273babcefb2 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 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