diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 4eeb42f70f35c..92d507a0e3a21 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9969,9 +9969,10 @@ package android.os { method public boolean isCloneProfile(); method public boolean isCredentialSharableWithParent(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int); method public boolean isMediaSharedWithParent(); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser(); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser(); method public static boolean isRemoveResultSuccessful(int); method public boolean isRestrictedProfile(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d1c1a17558d4b..3fee610943ba8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -905,6 +905,7 @@ package android.content.pm { method public boolean isFull(); method public boolean isGuest(); method public boolean isInitialized(); + method public boolean isMain(); method public boolean isManagedProfile(); method public boolean isPrimary(); method public boolean isProfile(); @@ -923,6 +924,7 @@ package android.content.pm { field public static final int FLAG_FULL = 1024; // 0x400 field @Deprecated public static final int FLAG_GUEST = 4; // 0x4 field public static final int FLAG_INITIALIZED = 16; // 0x10 + field public static final int FLAG_MAIN = 16384; // 0x4000 field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20 field public static final int FLAG_PRIMARY = 1; // 0x1 field public static final int FLAG_PROFILE = 4096; // 0x1000 diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 9baa6ba2fb496..2be0323a1e8b8 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -158,6 +158,18 @@ public class UserInfo implements Parcelable { */ public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000; + /** + * Indicates that this user is the designated main user on the device. This user may have access + * to certain features which are limited to at most one user. + * + *

Currently, this will be the first user to go through setup on the device, but in future + * releases this status may be transferable or may even not be given to any users. + * + *

This is not necessarily the system user. For example, it will not be the system user on + * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true. + */ + public static final int FLAG_MAIN = 0x00004000; + /** * @hide */ @@ -175,7 +187,8 @@ public class UserInfo implements Parcelable { FLAG_FULL, FLAG_SYSTEM, FLAG_PROFILE, - FLAG_EPHEMERAL_ON_CREATE + FLAG_EPHEMERAL_ON_CREATE, + FLAG_MAIN }) @Retention(RetentionPolicy.SOURCE) public @interface UserInfoFlag { @@ -368,6 +381,13 @@ public class UserInfo implements Parcelable { return (flags & FLAG_FULL) == FLAG_FULL; } + /** + * @see #FLAG_MAIN + */ + public boolean isMain() { + return (flags & FLAG_MAIN) == FLAG_MAIN; + } + /** * Returns true if the user is a split system user. *

If {@link UserManager#isSplitSystemUser split system user mode} is not enabled, diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index fbfe548416fe6..1f21bfe6cd729 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2341,12 +2341,18 @@ public class UserManager { } /** - * Used to check if the context user is the primary user. The primary user - * is the first human user on a device. This is not supported in headless system user mode. + * Used to check if the context user is the primary user. The primary user is the first human + * user on a device. This is not supported in headless system user mode. * * @return whether the context user is the primary user. + * + * @deprecated This method always returns true for the system user, who may not be a full user + * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser} + * or {@link #isMainUser} instead. + * * @hide */ + @Deprecated @SystemApi @RequiresPermission(anyOf = { Manifest.permission.MANAGE_USERS, @@ -2370,6 +2376,29 @@ public class UserManager { return getContextUserIfAppropriate() == UserHandle.USER_SYSTEM; } + /** + * Returns true if the context user is the designated "main user" of the device. This user may + * have access to certain features which are limited to at most one user. + * + *

Currently, the first human user on the device will be the main user; in the future, the + * concept may be transferable, so a different user (or even no user at all) may be designated + * the main user instead. + * + *

Note that this will be the not be the system user on devices for which + * {@link #isHeadlessSystemUserMode()} returns true. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS, + Manifest.permission.QUERY_USERS}) + @UserHandleAware + public boolean isMainUser() { + final UserInfo user = getUserInfo(mUserId); + return user != null && user.isMain(); + } + /** * Used to check if the context user is an admin user. An admin user is allowed to * modify or configure certain settings that aren't available to non-admin users, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index ac487fa0b1b7e..ce4a2ed488baa 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -265,7 +265,7 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100; - private static final int USER_VERSION = 10; + private static final int USER_VERSION = 11; private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms @@ -3355,6 +3355,7 @@ public class UserManagerService extends IUserManager.Stub { final int oldFlags = systemUserData.info.flags; final int newFlags; final String newUserType; + // TODO(b/256624031): Also handle FLAG_MAIN if (newHeadlessSystemUserMode) { newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS; newFlags = oldFlags & ~UserInfo.FLAG_FULL; @@ -3647,6 +3648,22 @@ public class UserManagerService extends IUserManager.Stub { userVersion = 10; } + if (userVersion < 11) { + // Add FLAG_MAIN + if (isHeadlessSystemUserMode()) { + final UserInfo earliestCreatedUser = getEarliestCreatedFullUser(); + earliestCreatedUser.flags |= UserInfo.FLAG_MAIN; + userIdsToWrite.add(earliestCreatedUser.id); + } else { + synchronized (mUsersLock) { + final UserData userData = mUsers.get(UserHandle.USER_SYSTEM); + userData.info.flags |= UserInfo.FLAG_MAIN; + userIdsToWrite.add(userData.info.id); + } + } + userVersion = 11; + } + // Reminder: If you add another upgrade, make sure to increment USER_VERSION too. // Done with userVersion changes, moving on to deal with userTypeVersion upgrades @@ -3776,6 +3793,21 @@ public class UserManagerService extends IUserManager.Stub { userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType); } + private UserInfo getEarliestCreatedFullUser() { + final List users = getUsersInternal(true, true, true); + UserInfo earliestUser = users.get(0); + long earliestCreationTime = earliestUser.creationTime; + for (int i = 0; i < users.size(); i++) { + final UserInfo info = users.get(i); + if (info.isFull() && info.creationTime > 0 + && info.creationTime < earliestCreationTime) { + earliestCreationTime = info.creationTime; + earliestUser = info; + } + } + return earliestUser; + } + @GuardedBy({"mPackagesLock"}) private void fallbackToSingleUserLP() { int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 871420a92f44b..8fb5773706c56 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -262,7 +262,8 @@ public final class UserTypeFactory { private static UserTypeDetails.Builder getDefaultTypeFullSystem() { return new UserTypeDetails.Builder() .setName(USER_TYPE_FULL_SYSTEM) - .setBaseType(FLAG_SYSTEM | FLAG_FULL); + .setBaseType(FLAG_SYSTEM | FLAG_FULL) + .setDefaultUserInfoPropertyFlags(UserInfo.FLAG_MAIN); } /** diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java index 46e59a9f10dd2..c3329795d4e20 100644 --- a/services/java/com/android/server/BootUserInitializer.java +++ b/services/java/com/android/server/BootUserInitializer.java @@ -83,9 +83,10 @@ final class BootUserInitializer { Slogf.d(TAG, "Creating initial user"); t.traceBegin("create-initial-user"); try { + int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN; // TODO(b/204091126): proper name for user UserInfo newUser = um.createUserEvenWhenDisallowed("Real User", - UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN, + UserManager.USER_TYPE_FULL_SECONDARY, flags, /* disallowedPackages= */ null, /* token= */ null); Slogf.i(TAG, "Created initial user: %s", newUser.toFullString()); initialUserId = newUser.id;