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.
*/
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
@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);
@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;
}
@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;
}
lastLoggedInTime = orig.lastLoggedInTime;
lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
partial = orig.partial;
+ preCreated = orig.preCreated;
profileGroupId = orig.profileGroupId;
restrictedProfileParentId = orig.restrictedProfileParentId;
guestToRemove = orig.guestToRemove;
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;
}
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);
}
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();
}
}
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;
}
}
- 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
// 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;
}
}
}
+ 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);
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);
}
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);
// 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,
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);
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
});
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);
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;
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;
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;
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";
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;
@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;
}
}
}
- 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;
}
@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");
}
@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();
// 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++;
}
}
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");
}
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;
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;
userInfo.lastLoggedInTime = lastLoggedInTime;
userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
userInfo.partial = partial;
+ userInfo.preCreated = preCreated;
userInfo.guestToRemove = guestToRemove;
userInfo.profileGroupId = profileGroupId;
userInfo.profileBadge = profileBadge;
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
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;
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();
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;
}
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.
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);
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.
+ *
+ * <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));
+ }
+ 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();
try {
switch(cmd) {
case "list":
- return runList(pw);
+ return runList(pw, shell);
default:
return shell.handleDefaultCommands(cmd);
}
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;
}
if (userInfo.partial) {
pw.print(" <partial>");
}
+ if (userInfo.preCreated) {
+ pw.print(" <pre-created>");
+ }
pw.println();
pw.print(" State: ");
final int state;
// 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) {
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);
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.");
}
}
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;
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;
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;
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
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);
});
}
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));
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) {