From f8a6722afc1da6ef30e7bcf79ca20f948a3657bf Mon Sep 17 00:00:00 2001 From: Oli Lan Date: Mon, 31 Oct 2022 16:15:33 +0000 Subject: [PATCH] Add isMainUser system API. This adds an API to determine if the context user is the designated main user on the device. This user may have access to features which are limited to a single user on the device. In Android U, this will be the first user to go through setup. On regular devices, this will be the system user, but on devices in No Primary User / Headless System User Mode, it will be a different user. The isPrimaryUser System API is confusing because it actually always returns the system user, even in headless mode. That API is deprecated in this change. On devices that are upgrading, if they are non-headless, the system user will be the main user. If they are headless, the full user with the earliest creation date will be given the flag. Bug: 256624031 Test: atest UserManagerTest Change-Id: Ib36f3b372f9bf33cbb097c4af63eb43515ac835b --- core/api/system-current.txt | 3 +- core/api/test-current.txt | 2 ++ core/java/android/content/pm/UserInfo.java | 22 +++++++++++- core/java/android/os/UserManager.java | 33 ++++++++++++++++-- .../android/server/pm/UserManagerService.java | 34 ++++++++++++++++++- .../android/server/pm/UserTypeFactory.java | 3 +- .../android/server/BootUserInitializer.java | 3 +- 7 files changed, 93 insertions(+), 7 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ae6e58c7ed486..3cf1e8c0a2cbd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9968,9 +9968,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 ae5a5ee88d98f..c40dae9171e78 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -904,6 +904,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(); @@ -922,6 +923,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 3d20d6373ae29..149e79a0f2fdf 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2306,12 +2306,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, @@ -2335,6 +2341,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 0eff9e9a8e4d9..68a0a2053b6aa 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -261,7 +261,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 @@ -3330,6 +3330,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; @@ -3622,6 +3623,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 @@ -3751,6 +3768,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 c35fe174415d1..1e1ada6be2c69 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -260,7 +260,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;