diff options
author | Adam Bookatz <bookatz@google.com> | 2019-11-15 19:15:37 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-11-15 19:15:37 +0000 |
commit | b9a57da94c5a3b4007307f05f0559b1dc5b56c0f (patch) | |
tree | b55d69677c56744b509ebd41a7fa7b2a1d63a7ec | |
parent | 77c54b3a3354315b8ecd5db92e2a24d45a3dbc83 (diff) | |
parent | 029832a9517e91b6d49d9e4e8ce6b6035f8e8327 (diff) |
Merge "Introduce user types"
25 files changed, 2122 insertions, 407 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index e74e4a958eb9..278a78676a0b 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -516,7 +516,7 @@ public class UserLifecycleTests { /** Creates a managed (work) profile under the current user, returning its userId. */ private int createManagedProfile() { final UserInfo userInfo = mUm.createProfileForUser("TestProfile", - UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser()); + UserManager.USER_TYPE_PROFILE_MANAGED, /* flags */ 0, mAm.getCurrentUser()); if (userInfo == null) { throw new IllegalStateException("Creating managed profile failed. Most likely there is " + "already a pre-existing profile on the device."); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 566134733b87..0113f6912183 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -92,7 +92,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; -import android.util.IconDrawableFactory; import android.util.LauncherIcons; import android.util.Log; import android.view.Display; @@ -1474,11 +1473,11 @@ public class ApplicationPackageManager extends PackageManager { @Override public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) { - if (!isManagedProfile(user.getIdentifier())) { + if (!hasUserBadge(user.getIdentifier())) { return icon; } Drawable badge = new LauncherIcons(mContext).getBadgeDrawable( - com.android.internal.R.drawable.ic_corp_icon_badge_case, + getUserManager().getUserIconBadgeResId(user.getIdentifier()), getUserBadgeColor(user)); return getBadgedDrawable(icon, badge, null, true); } @@ -1493,26 +1492,21 @@ public class ApplicationPackageManager extends PackageManager { return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true); } - @VisibleForTesting - public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] { - com.android.internal.R.string.managed_profile_label_badge, - com.android.internal.R.string.managed_profile_label_badge_2, - com.android.internal.R.string.managed_profile_label_badge_3 - }; - + /** Returns the color of the user's actual badge (not the badge's shadow). */ private int getUserBadgeColor(UserHandle user) { - return IconDrawableFactory.getUserBadgeColor(getUserManager(), user.getIdentifier()); + return getUserManager().getUserBadgeColor(user.getIdentifier()); } @Override public Drawable getUserBadgeForDensity(UserHandle user, int density) { - Drawable badgeColor = getManagedProfileIconForDensity(user, + // This is part of the shadow, not the main color, and is not actually corp-specific. + Drawable badgeColor = getProfileIconForDensity(user, com.android.internal.R.drawable.ic_corp_badge_color, density); if (badgeColor == null) { return null; } Drawable badgeForeground = getDrawableForDensity( - com.android.internal.R.drawable.ic_corp_badge_case, density); + getUserManager().getUserBadgeResId(user.getIdentifier()), density); badgeForeground.setTint(getUserBadgeColor(user)); Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground }); return badge; @@ -1520,8 +1514,8 @@ public class ApplicationPackageManager extends PackageManager { @Override public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) { - Drawable badge = getManagedProfileIconForDensity(user, - com.android.internal.R.drawable.ic_corp_badge_no_background, density); + Drawable badge = getProfileIconForDensity(user, + getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density); if (badge != null) { badge.setTint(getUserBadgeColor(user)); } @@ -1535,8 +1529,8 @@ public class ApplicationPackageManager extends PackageManager { return mContext.getResources().getDrawableForDensity(drawableId, density); } - private Drawable getManagedProfileIconForDensity(UserHandle user, int drawableId, int density) { - if (isManagedProfile(user.getIdentifier())) { + private Drawable getProfileIconForDensity(UserHandle user, int drawableId, int density) { + if (hasUserBadge(user.getIdentifier())) { return getDrawableForDensity(drawableId, density); } return null; @@ -1544,12 +1538,7 @@ public class ApplicationPackageManager extends PackageManager { @Override public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { - if (isManagedProfile(user.getIdentifier())) { - int badge = getUserManager().getManagedProfileBadge(user.getIdentifier()); - int resourceId = CORP_BADGE_LABEL_RES_ID[badge % CORP_BADGE_LABEL_RES_ID.length]; - return Resources.getSystem().getString(resourceId, label); - } - return label; + return getUserManager().getBadgedLabelForUser(label, user); } @Override @@ -2865,8 +2854,8 @@ public class ApplicationPackageManager extends PackageManager { return drawable; } - private boolean isManagedProfile(int userId) { - return getUserManager().isManagedProfile(userId); + private boolean hasUserBadge(int userId) { + return getUserManager().hasBadge(userId); } /** diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 1e88ce7e3f5c..42d64d81b17e 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -17,6 +17,7 @@ package android.content.pm; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.annotation.UserIdInt; import android.os.Parcel; @@ -25,6 +26,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.DebugUtils; +import com.android.server.pm.UserTypeDetails; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,13 +35,13 @@ import java.lang.annotation.RetentionPolicy; * Per-user information. * * <p>There are 3 base properties of users: {@link #FLAG_SYSTEM}, {@link #FLAG_FULL}, and - * {@link #FLAG_MANAGED_PROFILE}. Every user must have one of the following combination of these + * {@link #FLAG_PROFILE}. Every user must have one of the following combination of these * flags: * <ul> * <li>FLAG_SYSTEM (user {@link UserHandle#USER_SYSTEM} on a headless-user-0 device)</li> * <li>FLAG_SYSTEM and FLAG_FULL (user {@link UserHandle#USER_SYSTEM} on a regular device)</li> * <li>FLAG_FULL (non-profile secondary user)</li> - * <li>FLAG_MANAGED_PROFILE (profile users)</li> + * <li>FLAG_PROFILE (profile users)</li> * </ul> * Users can have also have additional flags (such as FLAG_GUEST) as appropriate. * @@ -70,13 +73,17 @@ public class UserInfo implements Parcelable { /** * Indicates a guest user that may be transient. + * @deprecated Use {@link UserManager#USER_TYPE_FULL_GUEST} instead. */ + @Deprecated public static final int FLAG_GUEST = 0x00000004; /** * Indicates the user has restrictions in privileges, in addition to those for normal users. * Exact meaning TBD. For instance, maybe they can't install apps or administer WiFi access pts. + * @deprecated Use {@link UserManager#USER_TYPE_FULL_RESTRICTED} instead. */ + @Deprecated public static final int FLAG_RESTRICTED = 0x00000008; /** @@ -87,7 +94,9 @@ public class UserInfo implements Parcelable { /** * Indicates that this user is a profile of another user, for example holding a users * corporate data. + * @deprecated Use {@link UserManager#USER_TYPE_PROFILE_MANAGED} instead. */ + @Deprecated public static final int FLAG_MANAGED_PROFILE = 0x00000020; /** @@ -108,14 +117,16 @@ public class UserInfo implements Parcelable { /** * User is for demo purposes only and can be removed at any time. + * @deprecated Use {@link UserManager#USER_TYPE_FULL_DEMO} instead. */ + @Deprecated public static final int FLAG_DEMO = 0x00000200; /** * Indicates that this user is a non-profile human user. * * <p>When creating a new (non-system) user, this flag will always be forced true unless the - * user is a {@link #FLAG_MANAGED_PROFILE}. If user {@link UserHandle#USER_SYSTEM} is also a + * user is a {@link #FLAG_PROFILE}. If user {@link UserHandle#USER_SYSTEM} is also a * human user, it must also be flagged as FULL. */ public static final int FLAG_FULL = 0x00000400; @@ -126,11 +137,10 @@ public class UserInfo implements Parcelable { public static final int FLAG_SYSTEM = 0x00000800; /** - * Indicates that this user is some sort of profile. Right now, the only profile type is - * {@link #FLAG_MANAGED_PROFILE}, but this can include other types of profiles too if any - * are created in the future. This is therefore not a flag, but an OR of several flags. + * Indicates that this user is a profile human user, such as a managed profile. + * Mutually exclusive with {@link #FLAG_FULL}. */ - public static final int PROFILE_FLAGS_MASK = FLAG_MANAGED_PROFILE; + public static final int FLAG_PROFILE = 0x00001000; /** * @hide @@ -147,7 +157,8 @@ public class UserInfo implements Parcelable { FLAG_EPHEMERAL, FLAG_DEMO, FLAG_FULL, - FLAG_SYSTEM + FLAG_SYSTEM, + FLAG_PROFILE }) @Retention(RetentionPolicy.SOURCE) public @interface UserInfoFlag { @@ -170,6 +181,13 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public long lastLoggedInTime; public String lastLoggedInFingerprint; + + /** + * Type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}, corresponding to + * {@link UserTypeDetails#getName()}. + */ + public String userType; + /** * If this user is a parent user, it would be its own user id. * If this user is a child user, it would be its parent user id. @@ -178,7 +196,12 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public int profileGroupId; public int restrictedProfileParentId; - /** Which profile badge color/label to use. */ + + /** + * Which badge color/label to use within a particular {@link UserTypeDetails}, i.e. + * the badgeIndex. + * This is an index for distinguishing different profiles with the same parent and user type. + */ public int profileBadge; /** User is only partially created. */ @@ -199,21 +222,68 @@ public class UserInfo implements Parcelable { */ public boolean preCreated; + /** + * Creates a UserInfo whose user type is determined automatically by the flags according to + * {@link #getDefaultUserType}; can only be used for user types handled there. + */ @UnsupportedAppUsage public UserInfo(int id, String name, int flags) { this(id, name, null, flags); } + /** + * Creates a UserInfo whose user type is determined automatically by the flags according to + * {@link #getDefaultUserType}; can only be used for user types handled there. + */ @UnsupportedAppUsage public UserInfo(int id, String name, String iconPath, int flags) { + this(id, name, iconPath, flags, getDefaultUserType(flags)); + } + + public UserInfo(int id, String name, String iconPath, int flags, String userType) { this.id = id; this.name = name; this.flags = flags; + this.userType = userType; this.iconPath = iconPath; this.profileGroupId = NO_PROFILE_GROUP_ID; this.restrictedProfileParentId = NO_PROFILE_GROUP_ID; } + /** + * Get the user type (such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}) that corresponds to + * the given {@link UserInfoFlag}s. + + * <p>The userInfoFlag can contain GUEST, RESTRICTED, MANAGED_PROFILE, DEMO, or else be + * interpreted as a regular "secondary" user. It cannot contain more than one of these. + * It can contain other UserInfoFlag properties (like EPHEMERAL), which will be ignored here. + * + * @throws IllegalArgumentException if userInfoFlag is more than one type of user or if it + * is a SYSTEM user. + * + * @hide + */ + public static @NonNull String getDefaultUserType(@UserInfoFlag int userInfoFlag) { + if ((userInfoFlag & FLAG_SYSTEM) != 0) { + throw new IllegalArgumentException("Cannot getDefaultUserType for flags " + + Integer.toHexString(userInfoFlag) + " because it corresponds to a " + + "SYSTEM user type."); + } + final int supportedFlagTypes = + FLAG_GUEST | FLAG_RESTRICTED | FLAG_MANAGED_PROFILE | FLAG_DEMO; + switch (userInfoFlag & supportedFlagTypes) { + case 0 : return UserManager.USER_TYPE_FULL_SECONDARY; + case FLAG_GUEST: return UserManager.USER_TYPE_FULL_GUEST; + case FLAG_RESTRICTED: return UserManager.USER_TYPE_FULL_RESTRICTED; + case FLAG_MANAGED_PROFILE: return UserManager.USER_TYPE_PROFILE_MANAGED; + case FLAG_DEMO: return UserManager.USER_TYPE_FULL_DEMO; + default: + throw new IllegalArgumentException("Cannot getDefaultUserType for flags " + + Integer.toHexString(userInfoFlag) + " because it doesn't correspond to a " + + "valid user type."); + } + } + @UnsupportedAppUsage public boolean isPrimary() { return (flags & FLAG_PRIMARY) == FLAG_PRIMARY; @@ -226,31 +296,21 @@ 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; + return UserManager.isUserTypeGuest(userType); } @UnsupportedAppUsage public boolean isRestricted() { - return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED; + return UserManager.isUserTypeRestricted(userType); } - @UnsupportedAppUsage - public boolean isManagedProfile() { - return isManagedProfile(flags); + public boolean isProfile() { + return (flags & FLAG_PROFILE) != 0; } - /** - * Checks if the flag denotes a managed profile. - */ - public static boolean isManagedProfile(@UserInfoFlag int flags) { - return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE; + @UnsupportedAppUsage + public boolean isManagedProfile() { + return UserManager.isUserTypeManagedProfile(userType); } @UnsupportedAppUsage @@ -271,7 +331,7 @@ public class UserInfo implements Parcelable { } public boolean isDemo() { - return (flags & FLAG_DEMO) == FLAG_DEMO; + return UserManager.isUserTypeDemo(userType); } public boolean isFull() { @@ -304,7 +364,7 @@ public class UserInfo implements Parcelable { // Don't support switching to an ephemeral user with removal in progress. return false; } - return !isManagedProfile(); + return !isProfile(); } /** @@ -316,9 +376,10 @@ public class UserInfo implements Parcelable { return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo(); } + // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType)) /* @hide */ public boolean canHaveProfile() { - if (isManagedProfile() || isGuest() || isRestricted()) { + if (isProfile() || isGuest() || isRestricted()) { return false; } if (UserManager.isSplitSystemUser() || UserManager.isHeadlessSystemUserMode()) { @@ -336,6 +397,7 @@ public class UserInfo implements Parcelable { iconPath = orig.iconPath; id = orig.id; flags = orig.flags; + userType = orig.userType; serialNumber = orig.serialNumber; creationTime = orig.creationTime; lastLoggedInTime = orig.lastLoggedInTime; @@ -353,6 +415,7 @@ public class UserInfo implements Parcelable { return UserHandle.of(id); } + // TODO(b/142482943): Probably include mUserType here, which means updating TestDevice, etc. @Override public String toString() { // NOTE: do not change this string, it's used by 'pm list users', which in turn is @@ -365,6 +428,7 @@ public class UserInfo implements Parcelable { public String toFullString() { return "UserInfo[id=" + id + ", name=" + name + + ", type=" + userType + ", flags=" + flagsToString(flags) + (preCreated ? " (pre-created)" : "") + (partial ? " (partial)" : "") @@ -387,6 +451,7 @@ public class UserInfo implements Parcelable { dest.writeString(name); dest.writeString(iconPath); dest.writeInt(flags); + dest.writeString(userType); dest.writeInt(serialNumber); dest.writeLong(creationTime); dest.writeLong(lastLoggedInTime); @@ -415,6 +480,7 @@ public class UserInfo implements Parcelable { name = source.readString(); iconPath = source.readString(); flags = source.readInt(); + userType = source.readString(); serialNumber = source.readInt(); creationTime = source.readLong(); lastLoggedInTime = source.readLong(); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index e8cc73f43a3d..40048d9154de 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -35,77 +35,84 @@ interface IUserManager { /* * DO NOT MOVE - UserManager.h depends on the ordering of this function. */ - int getCredentialOwnerProfile(int userHandle); - int getProfileParentId(int userHandle); + int getCredentialOwnerProfile(int userId); + int getProfileParentId(int userId); /* * END OF DO NOT MOVE */ - UserInfo createUser(in String name, int flags); - UserInfo preCreateUser(int flags); - UserInfo createProfileForUser(in String name, int flags, int userHandle, + UserInfo createUser(in String name, in String userType, int flags); + UserInfo preCreateUser(in String userType); + UserInfo createProfileForUser(in String name, in String userType, int flags, int userId, in String[] disallowedPackages); UserInfo createRestrictedProfile(String name, int parentUserHandle); - void setUserEnabled(int userHandle); + void setUserEnabled(int userId); void setUserAdmin(int userId); - void evictCredentialEncryptionKey(int userHandle); - boolean removeUser(int userHandle); - boolean removeUserEvenWhenDisallowed(int userHandle); - void setUserName(int userHandle, String name); - void setUserIcon(int userHandle, in Bitmap icon); - ParcelFileDescriptor getUserIcon(int userHandle); + void evictCredentialEncryptionKey(int userId); + boolean removeUser(int userId); + boolean removeUserEvenWhenDisallowed(int userId); + void setUserName(int userId, String name); + void setUserIcon(int userId, in Bitmap icon); + ParcelFileDescriptor getUserIcon(int userId); UserInfo getPrimaryUser(); List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated); - List<UserInfo> getProfiles(int userHandle, boolean enabledOnly); + List<UserInfo> getProfiles(int userId, boolean enabledOnly); int[] getProfileIds(int userId, boolean enabledOnly); - boolean canAddMoreManagedProfiles(int userHandle, boolean allowedToRemoveOne); - UserInfo getProfileParent(int userHandle); - boolean isSameProfileGroup(int userHandle, int otherUserHandle); + boolean canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne); + boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne); + UserInfo getProfileParent(int userId); + boolean isSameProfileGroup(int userId, int otherUserHandle); + String getUserTypeForUser(int userId); @UnsupportedAppUsage - UserInfo getUserInfo(int userHandle); - String getUserAccount(int userHandle); - void setUserAccount(int userHandle, String accountName); - long getUserCreationTime(int userHandle); + UserInfo getUserInfo(int userId); + String getUserAccount(int userId); + void setUserAccount(int userId, String accountName); + long getUserCreationTime(int userId); boolean isRestricted(); - boolean canHaveRestrictedProfile(int userHandle); - int getUserSerialNumber(int userHandle); + boolean canHaveRestrictedProfile(int userId); + int getUserSerialNumber(int userId); int getUserHandle(int userSerialNumber); - int getUserRestrictionSource(String restrictionKey, int userHandle); - List<UserManager.EnforcingUser> getUserRestrictionSources(String restrictionKey, int userHandle); - Bundle getUserRestrictions(int userHandle); - boolean hasBaseUserRestriction(String restrictionKey, int userHandle); - boolean hasUserRestriction(in String restrictionKey, int userHandle); + int getUserRestrictionSource(String restrictionKey, int userId); + List<UserManager.EnforcingUser> getUserRestrictionSources(String restrictionKey, int userId); + Bundle getUserRestrictions(int userId); + boolean hasBaseUserRestriction(String restrictionKey, int userId); + boolean hasUserRestriction(in String restrictionKey, int userId); boolean hasUserRestrictionOnAnyUser(in String restrictionKey); boolean isSettingRestrictedForUser(in String setting, int userId, in String value, int callingUid); void addUserRestrictionsListener(IUserRestrictionsListener listener); - void setUserRestriction(String key, boolean value, int userHandle); - void setApplicationRestrictions(in String packageName, in Bundle restrictions, - int userHandle); + void setUserRestriction(String key, boolean value, int userId); + void setApplicationRestrictions(in String packageName, in Bundle restrictions, int userId); Bundle getApplicationRestrictions(in String packageName); - Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle); + Bundle getApplicationRestrictionsForUser(in String packageName, int userId); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); - boolean markGuestForDeletion(int userHandle); - boolean isQuietModeEnabled(int userHandle); - void setSeedAccountData(int userHandle, in String accountName, + boolean markGuestForDeletion(int userId); + boolean isQuietModeEnabled(int userId); + void setSeedAccountData(int userId, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); String getSeedAccountName(); String getSeedAccountType(); PersistableBundle getSeedAccountOptions(); void clearSeedAccountData(); boolean someUserHasSeedAccount(in String accountName, in String accountType); + boolean isProfile(int userId); boolean isManagedProfile(int userId); boolean isDemoUser(int userId); boolean isPreCreated(int userId); - UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle, - in String[] disallowedPackages); + UserInfo createProfileForUserEvenWhenDisallowed(in String name, in String userType, int flags, + int userId, in String[] disallowedPackages); boolean isUserUnlockingOrUnlocked(int userId); - int getManagedProfileBadge(int userId); + int getUserIconBadgeResId(int userId); + int getUserBadgeResId(int userId); + int getUserBadgeNoBackgroundResId(int userId); + int getUserBadgeLabelResId(int userId); + int getUserBadgeColorResId(int userId); + boolean hasBadge(int userId); boolean isUserUnlocked(int userId); boolean isUserRunning(int userId); - boolean isUserNameSet(int userHandle); + boolean isUserNameSet(int userId); boolean hasRestrictedProfiles(); - boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target); + boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target); String getUserName(); long getUserStartRealtime(); long getUserUnlockRealtime(); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index b096049ebb68..ed21b7200707 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -18,6 +18,8 @@ package android.os; import android.Manifest; import android.accounts.AccountManager; +import android.annotation.ColorInt; +import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -76,6 +78,57 @@ public class UserManager { private final Context mContext; private Boolean mIsManagedProfileCached; + private Boolean mIsProfileCached; + + /** + * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user. + * This type of user cannot be created; it can only pre-exist on first boot. + * @hide + */ + public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; + + /** + * User type representing a regular non-profile non-{@link UserHandle#USER_SYSTEM system} human + * user. + * This is sometimes called an ordinary 'secondary user'. + * @hide + */ + public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; + + /** + * User type representing a guest user that may be transient. + * @hide + */ + public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; + + /** + * User type representing a user for demo purposes only, which can be removed at any time. + * @hide + */ + public static final String USER_TYPE_FULL_DEMO = "android.os.usertype.full.DEMO"; + + /** + * User type representing a "restricted profile" user, which is a full user that is subject to + * certain restrictions from a parent user. Note, however, that it is NOT technically a profile. + * @hide + */ + public static final String USER_TYPE_FULL_RESTRICTED = "android.os.usertype.full.RESTRICTED"; + + /** + * User type representing a managed profile, which is a profile that is to be managed by a + * device policy controller (DPC). + * The intended purpose is for work profiles, which are managed by a corporate entity. + * @hide + */ + public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; + + /** + * User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a + * human user. + * This type of user cannot be created; it can only pre-exist on first boot. + * @hide + */ + public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; /** * @hide @@ -1479,6 +1532,79 @@ public class UserManager { } /** + * Returns the calling user's user type. + * + * // TODO(b/142482943): Decide on the appropriate permission requirements. + * + * @return the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @hide + */ + public @NonNull String getUserType() { + try { + return mService.getUserTypeForUser(UserHandle.myUserId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the given user's user type. + * + * // TODO(b/142482943): Decide on the appropriate permission requirements. + * Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller + * must be in the same profile group of specified user. + * + * @param userHandle the user handle of the user whose type is being requested. + * @return the name of the user's user type, e.g. {@link UserManager#USER_TYPE_PROFILE_MANAGED}, + * or {@code null} if there is no such user. + * @hide + */ + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + public @Nullable String getUserTypeForUser(@NonNull UserHandle userHandle) { + try { + return mService.getUserTypeForUser(userHandle.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the user type is a + * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}. + * @hide + */ + public static boolean isUserTypeManagedProfile(String userType) { + return USER_TYPE_PROFILE_MANAGED.equals(userType); + } + + /** + * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}. + * @hide + */ + public static boolean isUserTypeGuest(String userType) { + return USER_TYPE_FULL_GUEST.equals(userType); + } + + /** + * Returns whether the user type is a + * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}. + * @hide + */ + public static boolean isUserTypeRestricted(String userType) { + return USER_TYPE_FULL_RESTRICTED.equals(userType); + } + + /** + * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}. + * @hide + */ + public static boolean isUserTypeDemo(String userType) { + return USER_TYPE_FULL_DEMO.equals(userType); + } + + /** * @hide * @deprecated Use {@link #isRestrictedProfile()} */ @@ -1589,6 +1715,48 @@ public class UserManager { } /** + * Checks if the calling app is running in a profile. + * + * @return whether the caller is in a profile. + * @hide + */ + public boolean isProfile() { + // No need for synchronization. Once it becomes non-null, it'll be non-null forever. + // Worst case we might end up calling the AIDL method multiple times but that's fine. + if (mIsProfileCached != null) { + return mIsProfileCached; + } + try { + mIsProfileCached = mService.isProfile(UserHandle.myUserId()); + return mIsProfileCached; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Checks if the specified user is a profile. + * + * Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller + * must be in the same profile group of specified user. + * + * @return whether the specified user is a profile. + * @hide + */ + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + public boolean isProfile(@UserIdInt int userId) { + if (userId == UserHandle.myUserId()) { + return isProfile(); + } + try { + return mService.isProfile(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Checks if the calling app is running in a managed profile. * * @return whether the caller is in a managed profile. @@ -1612,7 +1780,8 @@ public class UserManager { /** * Checks if the specified user is a managed profile. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller + * Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller * must be in the same profile group of specified user. * * @return whether the specified user is a managed profile. @@ -1632,23 +1801,6 @@ public class UserManager { } /** - * Gets badge for a managed profile. - * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller - * must be in the same profile group of specified user. - * - * @return which badge to use for the managed profile badge id will be less than - * UserManagerService.getMaxManagedProfiles() - * @hide - */ - public int getManagedProfileBadge(@UserIdInt int userId) { - try { - return mService.getManagedProfileBadge(userId); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Checks if the calling app is running as an ephemeral user. * * @return whether the caller is an ephemeral user. @@ -2120,23 +2272,44 @@ 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. + * + * @param name the user's name + * @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. + * @throws IllegalArgumentException if flags do not correspond to a valid user type. + * @deprecated Use {@link #createUser(String, String, int)} instead. + * @hide + */ + @UnsupportedAppUsage + @Deprecated + public @Nullable UserInfo createUser(@Nullable String name, @UserInfoFlag int flags) { + return createUser(name, UserInfo.getDefaultUserType(flags), flags); + } + + /** + * Creates a user with the specified name and options. For non-admin users, default user * restrictions will be applied. * * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @param name the user's name - * @param flags UserInfo flags that identify the type of user and other properties. + * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. + * @param flags UserInfo flags that specify user properties. * @see UserInfo * * @return the UserInfo object for the created user, or {@code null} if the user could not be * created. * @hide */ - @UnsupportedAppUsage - public @Nullable UserInfo createUser(@Nullable String name, @UserInfoFlag int flags) { + public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType, + @UserInfoFlag int flags) { UserInfo user = null; try { - user = mService.createUser(name, flags); + user = mService.createUser(name, userType, flags); // TODO: Keep this in sync with // UserManagerService.LocalService.createUserEvenWhenDisallowed if (user != null && !user.isAdmin() && !user.isDemo()) { @@ -2150,19 +2323,17 @@ public class UserManager { } /** - * Pre-creates a user with the specified name and options. For non-admin users, default user + * Pre-creates a user of the specified type. For non-admin users, default user * restrictions will be applied. * * <p>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. + * by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it + * takes less time. * * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * - * @param flags UserInfo flags that identify the type of user and other properties. - * @see UserInfo - * + * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. * @return the UserInfo object for the created user, or {@code null} if the user could not be * created. * @@ -2171,9 +2342,9 @@ public class UserManager { * * @hide */ - public @Nullable UserInfo preCreateUser(@UserInfoFlag int flags) { + public @Nullable UserInfo preCreateUser(@NonNull String userType) { try { - return mService.preCreateUser(flags); + return mService.preCreateUser(userType); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2188,7 +2359,7 @@ public class UserManager { public UserInfo createGuest(Context context, String name) { UserInfo guest = null; try { - guest = mService.createUser(name, UserInfo.FLAG_GUEST); + guest = mService.createUser(name, USER_TYPE_FULL_GUEST, 0); if (guest != null) { Settings.Secure.putStringForUser(context.getContentResolver(), Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); @@ -2202,6 +2373,7 @@ public class UserManager { /** * Creates a user with the specified name and options as a profile of another user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * The type of profile must be specified using the given flags. * * @param name the user's name * @param flags flags that identify the type of user and other properties. @@ -2209,20 +2381,44 @@ public class UserManager { * * @return the {@link UserInfo} object for the created user, or null if the user * could not be created. + * @throws IllegalArgumentException if flags do not correspond to a valid user type. + * @deprecated Use {@link #createProfileForUser(String, String, int, int)} instead. * @hide */ @UnsupportedAppUsage - public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId) { - return createProfileForUser(name, flags, userId, null); + @Deprecated + public UserInfo createProfileForUser(String name, @UserInfoFlag int flags, + @UserIdInt int userId) { + return createProfileForUser(name, UserInfo.getDefaultUserType(flags), flags, + userId, null); } /** - * Version of {@link #createProfileForUser(String, int, int)} that allows you to specify + * Creates a user with the specified name and options as a profile of another user. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * @param name the user's name + * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @param flags UserInfo flags that specify user properties. + * @param userId new user will be a profile of this user. + * + * @return the {@link UserInfo} object for the created user, or null if the user + * could not be created. + * @hide + */ + public UserInfo createProfileForUser(String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId) { + return createProfileForUser(name, userType, flags, userId, null); + } + + /** + * Version of {@link #createProfileForUser(String, String, int, int)} that allows you to specify * any packages that should not be installed in the new profile by default, these packages can * still be installed later by the user if needed. * * @param name the user's name - * @param flags flags that identify the type of user and other properties. + * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @param flags UserInfo flags that specify user properties. * @param userId new user will be a profile of this user. * @param disallowedPackages packages that will not be installed in the profile being created. * @@ -2230,28 +2426,29 @@ public class UserManager { * could not be created. * @hide */ - public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId, - String[] disallowedPackages) { + public UserInfo createProfileForUser(String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { try { - return mService.createProfileForUser(name, flags, userId, disallowedPackages); + return mService.createProfileForUser(name, userType, flags, userId, disallowedPackages); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Similar to {@link #createProfileForUser(String, int, int, String[])} + * Similar to {@link #createProfileForUser(String, String, int, int, String[])} * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * - * @see #createProfileForUser(String, int, int, String[]) + * @see #createProfileForUser(String, String, int, int, String[]) * @hide */ - public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, - @UserIdInt int userId, String[] disallowedPackages) { + public UserInfo createProfileForUserEvenWhenDisallowed(String name, + @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, + String[] disallowedPackages) { try { - return mService.createProfileForUserEvenWhenDisallowed(name, flags, userId, - disallowedPackages); + return mService.createProfileForUserEvenWhenDisallowed(name, userType, flags, + userId, disallowedPackages); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2595,6 +2792,8 @@ public class UserManager { * @hide */ public boolean canAddMoreUsers() { + // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why + // not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles final List<UserInfo> users = getUsers(true); final int totalUserCount = users.size(); int aliveUserCount = 0; @@ -2625,6 +2824,22 @@ public class UserManager { } /** + * Checks whether it's possible to add more profiles of the given type to the given user. + * + * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @return true if more profiles can be added, false if limit has been reached. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean canAddMoreProfilesToUser(@NonNull String userType, @UserIdInt int userId) { + try { + return mService.canAddMoreProfilesToUser(userType, userId, false); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns list of the profiles of userId including userId itself. * Note that this returns both enabled and not enabled profiles. See * {@link #getEnabledProfiles(int)} if you need only the enabled ones. @@ -2858,8 +3073,112 @@ public class UserManager { } /** - * If the target user is a managed profile of the calling user or the caller - * is itself a managed profile, then this returns a badged copy of the given + * Returns whether the given user has a badge (generally to put on profiles' icons). + * + * @param userId userId of the user in question + * @return true if the user's icons should display a badge; false otherwise. + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public boolean hasBadge(@UserIdInt int userId) { + if (!isProfile(userId)) { + // Since currently only profiles actually have badges, we can do this optimization. + return false; + } + try { + return mService.hasBadge(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the calling app's user has a badge (generally to put on profiles' icons). + * + * @return true if the user's icons should display a badge; false otherwise. + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public boolean hasBadge() { + return hasBadge(UserHandle.myUserId()); + } + + /** + * Returns the badge color for the given user (generally to color a profile's icon's badge). + * + * <p>To check whether a badge color is expected for the user, first call {@link #hasBadge}. + * + * @return the color (not the resource ID) to be used for the user's badge + * @throws Resources.NotFoundException if no valid badge color exists for this user + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public @ColorInt int getUserBadgeColor(@UserIdInt int userId) { + try { + final int resourceId = mService.getUserBadgeColorResId(userId); + return Resources.getSystem().getColor(resourceId, null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the Resource ID of the user's icon badge. + * + * @return the Resource ID of the user's icon badge if it has one; otherwise + * {@link Resources#ID_NULL}. + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public @DrawableRes int getUserIconBadgeResId(@UserIdInt int userId) { + try { + return mService.getUserIconBadgeResId(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the Resource ID of the user's badge. + * + * @return the Resource ID of the user's badge if it has one; otherwise + * {@link Resources#ID_NULL}. + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public @DrawableRes int getUserBadgeResId(@UserIdInt int userId) { + try { + return mService.getUserBadgeResId(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the Resource ID of the user's badge without a background. + * + * @return the Resource ID of the user's no-background badge if it has one; otherwise + * {@link Resources#ID_NULL}. + * + * @see #getBadgedIconForUser more information about badging in general + * @hide + */ + public @DrawableRes int getUserBadgeNoBackgroundResId(@UserIdInt int userId) { + try { + return mService.getUserBadgeNoBackgroundResId(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * If the target user is a profile of the calling user or the caller + * is itself a profile, then this returns a badged copy of the given * icon to be able to distinguish it from the original icon. For badging an * arbitrary drawable use {@link #getBadgedDrawableForUser( * android.graphics.drawable.Drawable, UserHandle, android.graphics.Rect, int)}. @@ -2880,8 +3199,8 @@ public class UserManager { } /** - * If the target user is a managed profile of the calling user or the caller - * is itself a managed profile, then this returns a badged copy of the given + * If the target user is a profile of the calling user or the caller + * is itself a profile, then this returns a badged copy of the given * drawable allowing the user to distinguish it from the original drawable. * The caller can specify the location in the bounds of the drawable to be * badged where the badge should be applied as well as the density of the @@ -2911,11 +3230,15 @@ public class UserManager { } /** - * If the target user is a managed profile of the calling user or the caller - * is itself a managed profile, then this returns a copy of the label with + * If the target user is a profile of the calling user or the caller + * is itself a profile, then this returns a copy of the label with * badging for accessibility services like talkback. E.g. passing in "Email" * and it might return "Work Email" for Email in the work profile. * + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller + * must be in the same profile group of specified user. + * * @param label The label to change. * @param user The target user. * @return A label that combines the original label and a badge as @@ -2923,7 +3246,16 @@ public class UserManager { * @removed */ public CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user) { - return mContext.getPackageManager().getUserBadgedLabel(label, user); + final int userId = user.getIdentifier(); + if (!hasBadge(userId)) { + return label; + } + try { + final int resourceId = mService.getUserBadgeLabelResId(userId); + return Resources.getSystem().getString(resourceId, label); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } /** diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java index d90b65e22171..d86ebf3a44b3 100644 --- a/core/java/android/util/IconDrawableFactory.java +++ b/core/java/android/util/IconDrawableFactory.java @@ -26,8 +26,6 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; -import com.android.internal.annotations.VisibleForTesting; - /** * Utility class to load app drawables with appropriate badging. * @@ -78,10 +76,10 @@ public class IconDrawableFactory { com.android.internal.R.drawable.ic_instant_icon_badge_bolt, badgeColor); } - if (mUm.isManagedProfile(userId)) { + if (mUm.hasBadge(userId)) { icon = mLauncherIcons.getBadgedDrawable(icon, - com.android.internal.R.drawable.ic_corp_icon_badge_case, - getUserBadgeColor(mUm, userId)); + mUm.getUserIconBadgeResId(userId), + mUm.getUserBadgeColor(userId)); } return icon; } @@ -93,23 +91,6 @@ public class IconDrawableFactory { return mLauncherIcons.wrapIconDrawableWithShadow(icon); } - // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles() - @VisibleForTesting - public static final int[] CORP_BADGE_COLORS = new int[] { - com.android.internal.R.color.profile_badge_1, - com.android.internal.R.color.profile_badge_2, - com.android.internal.R.color.profile_badge_3 - }; - - public static int getUserBadgeColor(UserManager um, @UserIdInt int userId) { - int badge = um.getManagedProfileBadge(userId); - if (badge < 0) { - badge = 0; - } - int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length]; - return Resources.getSystem().getColor(resourceId, null); - } - @UnsupportedAppUsage public static IconDrawableFactory newInstance(Context context) { return new IconDrawableFactory(context, true); diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java index 8501eb5883d5..e652e17bceb3 100644 --- a/core/java/android/util/LauncherIcons.java +++ b/core/java/android/util/LauncherIcons.java @@ -106,9 +106,11 @@ public final class LauncherIcons { Resources overlayableRes = ActivityThread.currentActivityThread().getApplication().getResources(); + // ic_corp_icon_badge_shadow is not work-profile-specific. Drawable badgeShadow = overlayableRes.getDrawable( com.android.internal.R.drawable.ic_corp_icon_badge_shadow); + // ic_corp_icon_badge_color is not work-profile-specific. Drawable badgeColor = overlayableRes.getDrawable( com.android.internal.R.drawable.ic_corp_icon_badge_color) .getConstantState().newDrawable().mutate(); diff --git a/core/java/com/android/server/pm/UserTypeDetails.java b/core/java/com/android/server/pm/UserTypeDetails.java new file mode 100644 index 000000000000..5fc3ba1fc538 --- /dev/null +++ b/core/java/com/android/server/pm/UserTypeDetails.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.annotation.ColorRes; +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.StringRes; +import android.content.pm.UserInfo; +import android.content.pm.UserInfo.UserInfoFlag; +import android.content.res.Resources; +import android.os.UserManager; + +import com.android.internal.util.Preconditions; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Contains the details about a multiuser "user type", such as a + * {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * + * Tests are located in UserManagerServiceUserTypeTest.java. + * @hide + */ +public final class UserTypeDetails { + + /** Indicates that there is no limit to the number of users allowed. */ + public static final int UNLIMITED_NUMBER_OF_USERS = -1; + + /** Name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. */ + private final @NonNull String mName; + + // TODO(b/142482943): Currently unused. Hook this up. + private final boolean mEnabled; + + // TODO(b/142482943): Currently unused and not set. Hook this up. + private final int mLabel; + + /** + * Maximum number of this user type allowed on the device. + * Use {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. + */ + private final int mMaxAllowed; + + /** + * Maximum number of this user type allowed per parent (for user types, like profiles, that + * have parents). + * Use {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. + */ + // TODO(b/142482943): Should this also apply to restricted profiles? + private final int mMaxAllowedPerParent; + + // TODO(b/143784345): Update doc when we clean up UserInfo. + /** The {@link UserInfo.UserInfoFlag} representing the base type of this user. */ + private final @UserInfoFlag int mBaseType; + + // TODO(b/143784345): Update doc/name when we clean up UserInfo. + /** The {@link UserInfo.UserInfoFlag}s that all users of this type will automatically have. */ + private final @UserInfoFlag int mDefaultUserInfoPropertyFlags; + + // TODO(b/142482943): Hook these up to something and set them for each type. + private final List<String> mDefaultRestrictions; + + + // Fields for profiles only, controlling the nature of their badges. + // All badge information should be set if {@link #hasBadge()} is true. + + /** Resource ID of the badge put on icons. */ + private @DrawableRes final int mIconBadge; + /** Resource ID of the badge. Should be set if mIconBadge is set. */ + private @DrawableRes final int mBadgePlain; + /** Resource ID of the badge without a background. Should be set if mIconBadge is set. */ + private @DrawableRes final int mBadgeNoBackground; + + /** + * Resource ID ({@link StringRes}) of the of the labels to describe badged apps; should be the + * same format as com.android.internal.R.color.profile_badge_1. These are used for accessibility + * services. + * + * <p>This is an array because, in general, there may be multiple users of the same user type. + * In this case, the user is indexed according to its {@link UserInfo#profileBadge}. + * + * <p>Must be set if mIconBadge is set. + */ + private final int[] mBadgeLabels; + + /** + * Resource ID ({@link ColorRes}) of the colors badge put on icons. + * (The value is a resource ID referring to the color; it is not the color value itself). + * + * <p>This is an array because, in general, there may be multiple users of the same user type. + * In this case, the user is indexed according to its {@link UserInfo#profileBadge}. + * + * <p>Must be set if mIconBadge is set. + */ + private final int[] mBadgeColors; + + private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed, + @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label, + int maxAllowedPerParent, + int iconBadge, int badgePlain, int badgeNoBackground, + int[] badgeLabels, int[] badgeColors, + ArrayList<String> defaultRestrictions) { + this.mName = name; + this.mEnabled = enabled; + this.mMaxAllowed = maxAllowed; + this.mMaxAllowedPerParent = maxAllowedPerParent; + this.mBaseType = baseType; + this.mDefaultUserInfoPropertyFlags = defaultUserInfoPropertyFlags; + this.mDefaultRestrictions = + Collections.unmodifiableList(new ArrayList<>(defaultRestrictions)); + + this.mIconBadge = iconBadge; + this.mBadgePlain = badgePlain; + this.mBadgeNoBackground = badgeNoBackground; + this.mLabel = label; + this.mBadgeLabels = badgeLabels; + this.mBadgeColors = badgeColors; + } + + /** + * Returns the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + */ + public String getName() { + return mName; + } + + // TODO(b/142482943) Hook this up or delete it. + public boolean isEnabled() { + return mEnabled; + } + + /** + * Returns the maximum number of this user type allowed on the device. + * <p>Returns {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. + */ + public int getMaxAllowed() { + return mMaxAllowed; + } + + /** + * Returns the maximum number of this user type allowed per parent (for user types, like + * profiles, that have parents). + * <p>Returns {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. + */ + public int getMaxAllowedPerParent() { + return mMaxAllowedPerParent; + } + + // TODO(b/143784345): Update comment when UserInfo is reorganized. + /** The {@link UserInfo.UserInfoFlag}s that all users of this type will automatically have. */ + public int getDefaultUserInfoFlags() { + return mDefaultUserInfoPropertyFlags | mBaseType; + } + + // TODO(b/142482943) Hook this up; it is currently unused. + public int getLabel() { + return mLabel; + } + + /** Returns whether users of this user type should be badged. */ + public boolean hasBadge() { + return mIconBadge != Resources.ID_NULL; + } + + /** Resource ID of the badge put on icons. */ + public @DrawableRes int getIconBadge() { + return mIconBadge; + } + + /** Resource ID of the badge. Used for {@link UserManager#getUserBadgeResId(int)}. */ + public @DrawableRes int getBadgePlain() { + return mBadgePlain; + } + + /** Resource ID of the badge without a background. */ + public @DrawableRes int getBadgeNoBackground() { + return mBadgeNoBackground; + } + + /** + * Returns the Resource ID of the badgeIndexth badge label, where the badgeIndex is expected + * to be the {@link UserInfo#profileBadge} of the user. + * If badgeIndex exceeds the number of labels, returns the label for the highest index. + */ + public @StringRes int getBadgeLabel(int badgeIndex) { + if (mBadgeLabels == null || mBadgeLabels.length == 0 || badgeIndex < 0) { + return Resources.ID_NULL; + } + return mBadgeLabels[Math.min(badgeIndex, mBadgeLabels.length - 1)]; + } + + /** + * Returns the Resource ID of the badgeIndexth badge color, where the badgeIndex is expected + * to be the {@link UserInfo#profileBadge} of the user. + * If badgeIndex exceeds the number of colors, returns the color for the highest index. + */ + public @ColorRes int getBadgeColor(int badgeIndex) { + if (mBadgeColors == null || mBadgeColors.length == 0 || badgeIndex < 0) { + return Resources.ID_NULL; + } + return mBadgeColors[Math.min(badgeIndex, mBadgeColors.length - 1)]; + } + + public boolean isProfile() { + return (mBaseType & UserInfo.FLAG_PROFILE) != 0; + } + + // TODO(b/142482943): Hook this up and don't return the original. + public List<String> getDefaultRestrictions() { + return mDefaultRestrictions; + } + + /** Dumps details of the UserTypeDetails. Do not parse this. */ + public void dump(PrintWriter pw) { + final String prefix = " "; + pw.print(prefix); pw.print("mName: "); pw.println(mName); + pw.print(prefix); pw.print("mBaseType: "); pw.println(UserInfo.flagsToString(mBaseType)); + pw.print(prefix); pw.print("mEnabled: "); pw.println(mEnabled); + pw.print(prefix); pw.print("mMaxAllowed: "); pw.println(mMaxAllowed); + pw.print(prefix); pw.print("mMaxAllowedPerParent: "); pw.println(mMaxAllowedPerParent); + pw.print(prefix); pw.print("mDefaultUserInfoFlags: "); + pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags)); + pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel); + pw.print(prefix); pw.print("mDefaultRestrictions: "); pw.println(mDefaultRestrictions); + pw.print(prefix); pw.print("mIconBadge: "); pw.println(mIconBadge); + pw.print(prefix); pw.print("mBadgePlain: "); pw.println(mBadgePlain); + pw.print(prefix); pw.print("mBadgeNoBackground: "); pw.println(mBadgeNoBackground); + pw.print(prefix); pw.print("mBadgeLabels.length: "); + pw.println(mBadgeLabels != null ? mBadgeLabels.length : "0(null)"); + pw.print(prefix); pw.print("mBadgeColors.length: "); + pw.println(mBadgeColors != null ? mBadgeColors.length : "0(null)"); + } + + /** Builder for a {@link UserTypeDetails}; see that class for documentation. */ + public static final class Builder { + // UserTypeDetails properties and their default values. + private String mName; // This MUST be explicitly set. + private int mBaseType; // This MUST be explicitly set. + private int mMaxAllowed = UNLIMITED_NUMBER_OF_USERS; + private int mMaxAllowedPerParent = UNLIMITED_NUMBER_OF_USERS; + private int mDefaultUserInfoPropertyFlags = 0; + private ArrayList<String> mDefaultRestrictions = new ArrayList<>(); + private boolean mEnabled = true; + private int mLabel = Resources.ID_NULL; + private int[] mBadgeLabels = null; + private int[] mBadgeColors = null; + private int mIconBadge = Resources.ID_NULL; + private int mBadgePlain = Resources.ID_NULL; + private int mBadgeNoBackground = Resources.ID_NULL; + + public Builder setName(String name) { + mName = name; + return this; + } + + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + public Builder setMaxAllowed(int maxAllowed) { + mMaxAllowed = maxAllowed; + return this; + } + + public Builder setMaxAllowedPerParent(int maxAllowedPerParent) { + mMaxAllowedPerParent = maxAllowedPerParent; + return this; + } + + public Builder setBaseType(@UserInfoFlag int baseType) { + mBaseType = baseType; + return this; + } + + public Builder setDefaultUserInfoPropertyFlags(@UserInfoFlag int flags) { + mDefaultUserInfoPropertyFlags = flags; + return this; + } + + public Builder setBadgeLabels(int ... badgeLabels) { + mBadgeLabels = badgeLabels; + return this; + } + + public Builder setBadgeColors(int ... badgeColors) { + mBadgeColors = badgeColors; + return this; + } + + public Builder setIconBadge(int badgeIcon) { + mIconBadge = badgeIcon; + return this; + } + + public Builder setBadgePlain(int badgePlain) { + mBadgePlain = badgePlain; + return this; + } + + public Builder setBadgeNoBackground(int badgeNoBackground) { + mBadgeNoBackground = badgeNoBackground; + return this; + } + + public Builder setLabel(int label) { + mLabel = label; + return this; + } + + public Builder setDefaultRestrictions(ArrayList<String> restrictions) { + mDefaultRestrictions = restrictions; + return this; + } + + public UserTypeDetails createUserTypeDetails() { + Preconditions.checkArgument(mName != null, + "Cannot create a UserTypeDetails with no name."); + Preconditions.checkArgument(hasValidBaseType(), + "UserTypeDetails " + mName + " has invalid baseType: " + mBaseType); + Preconditions.checkArgument(hasValidPropertyFlags(), + "UserTypeDetails " + mName + " has invalid flags: " + + Integer.toHexString(mDefaultUserInfoPropertyFlags)); + if (hasBadge()) { + Preconditions.checkArgument(mBadgeLabels != null && mBadgeLabels.length != 0, + "UserTypeDetails " + mName + " has badge but no badgeLabels."); + Preconditions.checkArgument(mBadgeColors != null && mBadgeColors.length != 0, + "UserTypeDetails " + mName + " has badge but no badgeColors."); + } + + return new UserTypeDetails(mName, mEnabled, mMaxAllowed, mBaseType, + mDefaultUserInfoPropertyFlags, mLabel, mMaxAllowedPerParent, + mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors, + mDefaultRestrictions); + } + + private boolean hasBadge() { + return mIconBadge != Resources.ID_NULL; + } + + // TODO(b/143784345): Refactor this when we clean up UserInfo. + private boolean hasValidBaseType() { + return mBaseType == UserInfo.FLAG_FULL + || mBaseType == UserInfo.FLAG_PROFILE + || mBaseType == UserInfo.FLAG_SYSTEM + || mBaseType == (UserInfo.FLAG_FULL | UserInfo.FLAG_SYSTEM); + } + + // TODO(b/143784345): Refactor this when we clean up UserInfo. + private boolean hasValidPropertyFlags() { + final int forbiddenMask = + UserInfo.FLAG_PRIMARY | + UserInfo.FLAG_ADMIN | + UserInfo.FLAG_INITIALIZED | + UserInfo.FLAG_QUIET_MODE | + UserInfo.FLAG_FULL | + UserInfo.FLAG_SYSTEM | + UserInfo.FLAG_PROFILE; + return (mDefaultUserInfoPropertyFlags & forbiddenMask) == 0; + } + } + + /** + * Returns whether the user type is a managed profile + * (i.e. {@link UserManager#USER_TYPE_PROFILE_MANAGED}). + */ + public boolean isManagedProfile() { + return UserManager.isUserTypeManagedProfile(mName); + } +} diff --git a/core/java/com/android/server/pm/UserTypeFactory.java b/core/java/com/android/server/pm/UserTypeFactory.java new file mode 100644 index 000000000000..43bbab192722 --- /dev/null +++ b/core/java/com/android/server/pm/UserTypeFactory.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.UserInfo.FLAG_DEMO; +import static android.content.pm.UserInfo.FLAG_EPHEMERAL; +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_GUEST; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_PROFILE; +import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.content.pm.UserInfo.FLAG_SYSTEM; +import static android.os.UserManager.USER_TYPE_FULL_DEMO; +import static android.os.UserManager.USER_TYPE_FULL_GUEST; +import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED; +import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; +import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; + +import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS; + +import android.content.res.Resources; +import android.os.UserManager; +import android.util.ArrayMap; + +/** + * Class for creating all {@link UserTypeDetails} on the device. + * + * Tests are located in UserManagerServiceUserTypeTest.java. + * @hide + */ +public final class UserTypeFactory { + + /** This is a utility class, so no instantiable constructor. */ + private UserTypeFactory() {} + + /** + * Obtains the user types (built-in and customized) for this device. + * + * @return mapping from the name of each user type to its {@link UserTypeDetails} object + */ + public static ArrayMap<String, UserTypeDetails> getUserTypes() { + final ArrayMap<String, UserTypeDetails> map = new ArrayMap<>(); + // TODO(b/142482943): Read an xml file for OEM customized types. + // Remember to disallow "android." namespace + // TODO(b/142482943): Read an xml file to get any overrides for the built-in types. + final int maxManagedProfiles = 1; + map.put(USER_TYPE_PROFILE_MANAGED, + getDefaultTypeProfileManaged().setMaxAllowedPerParent(maxManagedProfiles) + .createUserTypeDetails()); + map.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeSystemFull().createUserTypeDetails()); + map.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary().createUserTypeDetails()); + map.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest().createUserTypeDetails()); + map.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo().createUserTypeDetails()); + map.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted().createUserTypeDetails()); + map.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless().createUserTypeDetails()); + return map; + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeProfileManaged() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_PROFILE_MANAGED) + .setBaseType(FLAG_PROFILE) + .setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE) + .setMaxAllowedPerParent(1) + .setLabel(0) + .setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case) + .setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case) + .setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background) + .setBadgeLabels( + com.android.internal.R.string.managed_profile_label_badge, + com.android.internal.R.string.managed_profile_label_badge_2, + com.android.internal.R.string.managed_profile_label_badge_3) + .setBadgeColors( + com.android.internal.R.color.profile_badge_1, + com.android.internal.R.color.profile_badge_2, + com.android.internal.R.color.profile_badge_3); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeFullSecondary() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_FULL_SECONDARY) + .setBaseType(FLAG_FULL) + .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_GUEST} configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeFullGuest() { + final boolean ephemeralGuests = Resources.getSystem() + .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral); + final int flags = FLAG_GUEST | (ephemeralGuests ? FLAG_EPHEMERAL : 0); + + // TODO(b/142482943): Put UMS.initDefaultGuestRestrictions() here; then fetch them from here + + return new UserTypeDetails.Builder() + .setName(USER_TYPE_FULL_GUEST) + .setBaseType(FLAG_FULL) + .setDefaultUserInfoPropertyFlags(flags) + .setMaxAllowed(1); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_DEMO} configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeFullDemo() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_FULL_DEMO) + .setBaseType(FLAG_FULL) + .setDefaultUserInfoPropertyFlags(FLAG_DEMO) + .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_RESTRICTED} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeFullRestricted() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_FULL_RESTRICTED) + .setBaseType(FLAG_FULL) + .setDefaultUserInfoPropertyFlags(FLAG_RESTRICTED) + .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeSystemFull() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_FULL_SYSTEM) + .setBaseType(FLAG_SYSTEM | FLAG_FULL); + } + + /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeSystemHeadless() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_SYSTEM_HEADLESS) + .setBaseType(FLAG_SYSTEM); + } +} diff --git a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java index 22b23148cbcf..1bc46a79f1c0 100644 --- a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import androidx.test.filters.LargeTest; @@ -40,6 +41,6 @@ public class ManagedUserContentResolverTest extends AbstractCrossUserContentReso @Override protected UserInfo createUser() throws RemoteException { return mUm.createProfileForUser("Managed user", - UserInfo.FLAG_MANAGED_PROFILE, UserHandle.myUserId()); + UserManager.USER_TYPE_PROFILE_MANAGED, /* flags */ 0, UserHandle.myUserId()); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 274696bfec0e..69487d508443 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -39,6 +39,7 @@ import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import android.os.UserManager; import com.android.settingslib.R; @@ -176,8 +177,8 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { boolean isManaged = context.getSystemService(DevicePolicyManager.class) .getProfileOwnerAsUser(userId) != null; if (isManaged) { - badge = getDrawableForDisplayDensity( - context, com.android.internal.R.drawable.ic_corp_badge_case); + badge = getDrawableForDisplayDensity(context, + context.getSystemService(UserManager.class).getUserBadgeResId(userId)); } } return setBadge(badge); diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java index e5f8b49c3f0c..9a7cb3f2eff2 100644 --- a/services/core/java/android/os/UserManagerInternal.java +++ b/services/core/java/android/os/UserManagerInternal.java @@ -143,8 +143,8 @@ public abstract class UserManagerInternal { * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when * createAndManageUser is called by the device owner. */ - public abstract UserInfo createUserEvenWhenDisallowed(String name, int flags, - String[] disallowedPackages); + public abstract UserInfo createUserEvenWhenDisallowed(String name, String userType, + int flags, String[] disallowedPackages); /** * Same as {@link UserManager#removeUser(int userId)}, but bypasses the check for @@ -202,8 +202,7 @@ public abstract class UserManagerInternal { /** * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group - * and that the {@code callingUserId} is not a managed profile and - * {@code targetUserId} is enabled. + * and that the {@code callingUserId} is not a profile and {@code targetUserId} is enabled. * * @return TRUE if the {@code callingUserId} can access {@code targetUserId}. FALSE * otherwise @@ -215,8 +214,7 @@ public abstract class UserManagerInternal { String debugMsg, boolean throwSecurityException); /** - * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return - * itself. + * If {@code userId} is of a profile, return the parent user ID. Otherwise return itself. */ public abstract int getProfileParentId(int userId); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 436167635662..31ceb38d1607 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -384,7 +384,7 @@ class UserController implements Handler.Callback { // We need to delay unlocking managed profiles until the parent user // is also unlocked. - if (mInjector.getUserManager().isManagedProfile(userId)) { + if (mInjector.getUserManager().isProfile(userId)) { final UserInfo parent = mInjector.getUserManager().getProfileParent(userId); if (parent != null && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 2b6c347fe726..bd9566728b1b 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -304,7 +304,7 @@ public class LauncherAppsService extends SystemService { long ident = injectClearCallingIdentity(); try { final UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); - if (callingUserInfo != null && callingUserInfo.isManagedProfile()) { + if (callingUserInfo != null && callingUserInfo.isProfile()) { Slog.w(TAG, message + " for another profile " + targetUserId + " from " + callingUserId + " not allowed"); return false; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 525d3578ccff..232374c6773e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2453,27 +2453,40 @@ class PackageManagerShellCommand extends ShellCommand { String name; int userId = -1; int flags = 0; + String userType = null; String opt; boolean preCreateOnly = false; while ((opt = getNextOption()) != null) { + String newUserType = null; if ("--profileOf".equals(opt)) { userId = UserHandle.parseUserArg(getNextArgRequired()); } else if ("--managed".equals(opt)) { - flags |= UserInfo.FLAG_MANAGED_PROFILE; + newUserType = UserManager.USER_TYPE_PROFILE_MANAGED; } else if ("--restricted".equals(opt)) { - flags |= UserInfo.FLAG_RESTRICTED; - } else if ("--ephemeral".equals(opt)) { - flags |= UserInfo.FLAG_EPHEMERAL; + newUserType = UserManager.USER_TYPE_FULL_RESTRICTED; } else if ("--guest".equals(opt)) { - flags |= UserInfo.FLAG_GUEST; + newUserType = UserManager.USER_TYPE_FULL_GUEST; } else if ("--demo".equals(opt)) { - flags |= UserInfo.FLAG_DEMO; + newUserType = UserManager.USER_TYPE_FULL_DEMO; + } else if ("--ephemeral".equals(opt)) { + flags |= UserInfo.FLAG_EPHEMERAL; } else if ("--pre-create-only".equals(opt)) { preCreateOnly = true; + } else if ("--user-type".equals(opt)) { + newUserType = getNextArgRequired(); } else { getErrPrintWriter().println("Error: unknown option " + opt); return 1; } + // Ensure only one user-type was specified. + if (newUserType != null) { + if (userType != null && !userType.equals(newUserType)) { + getErrPrintWriter().println("Error: more than one user type was specified (" + + userType + " and " + newUserType + ")"); + return 1; + } + userType = newUserType; + } } String arg = getNextArg(); if (arg == null && !preCreateOnly) { @@ -2490,16 +2503,20 @@ class PackageManagerShellCommand extends ShellCommand { ServiceManager.getService(Context.USER_SERVICE)); IAccountManager accm = IAccountManager.Stub.asInterface( ServiceManager.getService(Context.ACCOUNT_SERVICE)); - if ((flags & UserInfo.FLAG_RESTRICTED) != 0) { + if (userType == null) { + userType = UserInfo.getDefaultUserType(flags); + } + if (UserManager.isUserTypeRestricted(userType)) { // In non-split user mode, userId can only be SYSTEM int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; info = um.createRestrictedProfile(name, parentUserId); accm.addSharedAccountsFromParentUser(parentUserId, userId, (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); } else if (userId < 0) { - info = preCreateOnly ? um.preCreateUser(flags) : um.createUser(name, flags); + info = preCreateOnly ? + um.preCreateUser(userType) : um.createUser(name, userType, flags); } else { - info = um.createProfileForUser(name, flags, userId, null); + info = um.createProfileForUser(name, userType, flags, userId, null); } if (info != null) { @@ -3421,9 +3438,15 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" Lists the current users."); pw.println(""); pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]"); - pw.println(" [--guest] [--pre-create-only] USER_NAME"); + pw.println(" [--guest] [--pre-create-only] [--user-type USER_TYPE] USER_NAME"); pw.println(" Create a new user with the given USER_NAME, printing the new user identifier"); pw.println(" of the user."); + // TODO(b/142482943): Consider fetching the list of user types from UMS. + pw.println(" USER_TYPE is the name of a user type, e.g. android.os.usertype.profile.MANAGED."); + pw.println(" If not specified, the default user type is android.os.usertype.full.SECONDARY."); + pw.println(" --managed is shorthand for '--user-type android.os.usertype.profile.MANAGED'."); + pw.println(" --restricted is shorthand for '--user-type android.os.usertype.full.RESTRICTED'."); + pw.println(" --guest is shorthand for '--user-type android.os.usertype.full.GUEST'."); pw.println(""); pw.println(" remove-user USER_ID"); pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data"); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 5c9b9c93e4fe..8ddfad9ec81c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -20,8 +20,11 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.Manifest; +import android.annotation.ColorRes; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; @@ -77,6 +80,7 @@ import android.os.storage.StorageManager; import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; import android.stats.devicepolicy.DevicePolicyEnums; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.IntArray; @@ -152,6 +156,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String TAG_NAME = "name"; private static final String TAG_ACCOUNT = "account"; private static final String ATTR_FLAGS = "flags"; + private static final String ATTR_TYPE = "type"; private static final String ATTR_ICON_PATH = "icon"; private static final String ATTR_ID = "id"; private static final String ATTR_CREATION_TIME = "created"; @@ -220,15 +225,10 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100; - private static final int USER_VERSION = 8; + private static final int USER_VERSION = 9; private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms - // Maximum number of managed profiles permitted per user is 1. This cannot be increased - // without first making sure that the rest of the framework is prepared for it. - @VisibleForTesting - static final int MAX_MANAGED_PROFILES = 1; - static final int WRITE_USER_MSG = 1; static final int WRITE_USER_DELAY = 2*1000; // 2 seconds @@ -304,6 +304,12 @@ public class UserManagerService extends IUserManager.Stub { private final SparseArray<UserData> mUsers = new SparseArray<>(); /** + * Map of user type names to their corresponding {@link UserTypeDetails}. + * Should not be modified after UserManagerService constructor finishes. + */ + private final ArrayMap<String, UserTypeDetails> mUserTypes; + + /** * User restrictions set via UserManager. This doesn't include restrictions set by * device owner / profile owners. Only non-empty restriction bundles are stored. * @@ -543,6 +549,7 @@ public class UserManagerService extends IUserManager.Stub { mPackagesLock = packagesLock; mHandler = new MainHandler(); mUserDataPreparer = userDataPreparer; + mUserTypes = UserTypeFactory.getUserTypes(); synchronized (mPackagesLock) { mUsersDir = new File(dataDir, USER_INFO_DIR); mUsersDir.mkdirs(); @@ -700,22 +707,37 @@ public class UserManagerService extends IUserManager.Stub { final long ident = Binder.clearCallingIdentity(); try { synchronized (mUsersLock) { - return getProfilesLU(userId, enabledOnly, returnFullInfo); + return getProfilesLU(userId, /* userType */ null, enabledOnly, returnFullInfo); } } finally { Binder.restoreCallingIdentity(ident); } } + // TODO(b/142482943): Will probably need a getProfiles(userType). But permissions may vary. + @Override public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) { + return getProfileIds(userId, null, enabledOnly); + } + + // TODO(b/142482943): Probably @Override and make this accessible in UserManager. + /** + * Returns all the users of type userType that are in the same profile group as userId + * (including userId itself, if it is of the appropriate user type). + * + * <p>If userType is non-{@code null}, only returns users that are of type userType. + * If enabledOnly, only returns users that are not {@link UserInfo#FLAG_DISABLED}. + */ + public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType, + boolean enabledOnly) { if (userId != UserHandle.getCallingUserId()) { checkManageOrCreateUsersPermission("getting profiles related to user " + userId); } final long ident = Binder.clearCallingIdentity(); try { synchronized (mUsersLock) { - return getProfileIdsLU(userId, enabledOnly).toArray(); + return getProfileIdsLU(userId, userType, enabledOnly).toArray(); } } finally { Binder.restoreCallingIdentity(ident); @@ -724,9 +746,9 @@ public class UserManagerService extends IUserManager.Stub { /** Assume permissions already checked and caller's identity cleared */ @GuardedBy("mUsersLock") - private List<UserInfo> getProfilesLU(@UserIdInt int userId, boolean enabledOnly, - boolean fullInfo) { - IntArray profileIds = getProfileIdsLU(userId, enabledOnly); + private List<UserInfo> getProfilesLU(@UserIdInt int userId, @Nullable String userType, + boolean enabledOnly, boolean fullInfo) { + IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly); ArrayList<UserInfo> users = new ArrayList<>(profileIds.size()); for (int i = 0; i < profileIds.size(); i++) { int profileId = profileIds.get(i); @@ -746,9 +768,12 @@ public class UserManagerService extends IUserManager.Stub { /** * Assume permissions already checked and caller's identity cleared + * <p>If userType is {@code null}, returns all profiles for user; else, only returns + * profiles of that type. */ @GuardedBy("mUsersLock") - private IntArray getProfileIdsLU(@UserIdInt int userId, boolean enabledOnly) { + private IntArray getProfileIdsLU(@UserIdInt int userId, @Nullable String userType, + boolean enabledOnly) { UserInfo user = getUserInfoLU(userId); IntArray result = new IntArray(mUsers.size()); if (user == null) { @@ -770,6 +795,9 @@ public class UserManagerService extends IUserManager.Stub { if (profile.partial) { continue; } + if (userType != null && !userType.equals(profile.userType)) { + continue; + } result.add(profile.id); } return result; @@ -1116,6 +1144,45 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Returns the user type, e.g. {@link UserManager#USER_TYPE_FULL_GUEST}, of the given userId, + * or null if the user doesn't exist. + */ + @Override + public @Nullable String getUserTypeForUser(@UserIdInt int userId) { + // TODO(b/142482943): Decide on the appropriate permission requirements. + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserTypeForUser"); + return getUserTypeNoChecks(userId); + } + + /** + * Returns the user type of the given userId, or null if the user doesn't exist. + * <p>No permissions checks are made (but userId checks may be made). + */ + private @Nullable String getUserTypeNoChecks(@UserIdInt int userId) { + synchronized (mUsersLock) { + final UserInfo userInfo = getUserInfoLU(userId); + return userInfo != null ? userInfo.userType : null; + } + } + + /** + * Returns the UserTypeDetails of the given userId's user type, or null if the no such user. + * <p>No permissions checks are made (but userId checks may be made). + */ + private @Nullable UserTypeDetails getUserTypeDetailsNoChecks(@UserIdInt int userId) { + final String typeStr = getUserTypeNoChecks(userId); + return typeStr != null ? mUserTypes.get(typeStr) : null; + } + + /** + * Returns the UserTypeDetails of the given userInfo's user type (or null for a null userInfo). + */ + private @Nullable UserTypeDetails getUserTypeDetails(@Nullable UserInfo userInfo) { + final String typeStr = userInfo != null ? userInfo.userType : null; + return typeStr != null ? mUserTypes.get(typeStr) : null; + } + @Override public UserInfo getUserInfo(@UserIdInt int userId) { checkManageOrCreateUsersPermission("query user"); @@ -1139,11 +1206,78 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public int getManagedProfileBadge(@UserIdInt int userId) { - checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getManagedProfileBadge"); + public boolean hasBadge(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "hasBadge"); + final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId); + return userTypeDetails != null && userTypeDetails.hasBadge(); + } + + @Override + public @StringRes int getUserBadgeLabelResId(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeLabelResId"); + final UserInfo userInfo = getUserInfoNoChecks(userId); + final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo); + if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) { + Slog.e(LOG_TAG, "Requested badge label for non-badged user " + userId); + return Resources.ID_NULL; + } + final int badgeIndex = userInfo.profileBadge; + return userTypeDetails.getBadgeLabel(badgeIndex); + } + + @Override + public @ColorRes int getUserBadgeColorResId(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeColorResId"); + final UserInfo userInfo = getUserInfoNoChecks(userId); + final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo); + if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) { + Slog.e(LOG_TAG, "Requested badge color for non-badged user " + userId); + return Resources.ID_NULL; + } + final int badgeIndex = userInfo.profileBadge; + return userTypeDetails.getBadgeColor(badgeIndex); + } + + @Override + public @DrawableRes int getUserIconBadgeResId(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserIconBadgeResId"); + final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId); + if (userTypeDetails == null || !userTypeDetails.hasBadge()) { + Slog.e(LOG_TAG, "Requested icon badge for non-badged user " + userId); + return Resources.ID_NULL; + } + return userTypeDetails.getIconBadge(); + } + + @Override + public @DrawableRes int getUserBadgeResId(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeResId"); + final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId); + if (userTypeDetails == null || !userTypeDetails.hasBadge()) { + Slog.e(LOG_TAG, "Requested badge for non-badged user " + userId); + return Resources.ID_NULL; + } + return userTypeDetails.getBadgePlain(); + } + + @Override + public @DrawableRes int getUserBadgeNoBackgroundResId(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, + "getUserBadgeNoBackgroundResId"); + final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId); + if (userTypeDetails == null || !userTypeDetails.hasBadge()) { + Slog.e(LOG_TAG, "Requested badge (no background) for non-badged user " + userId); + return Resources.ID_NULL; + } + return userTypeDetails.getBadgeNoBackground(); + } + + @Override + public boolean isProfile(@UserIdInt int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isProfile"); synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); - return userInfo != null ? userInfo.profileBadge : 0; + return userInfo != null && userInfo.isProfile(); } } @@ -1896,33 +2030,93 @@ public class UserManagerService extends IUserManager.Stub { return count >= UserManager.getMaxSupportedUsers(); } + /** + * Returns whether more users of the given type can be added (based on how many users of that + * type already exist). + * + * <p>For checking whether more profiles can be added to a particular parent use + * {@link #canAddMoreProfilesToUser}. + */ + private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) { + final int max = userTypeDetails.getMaxAllowed(); + if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) { + return true; // Indicates that there is no max. + } + return getNumberOfUsersOfType(userTypeDetails.getName()) < max; + } + + /** + * Gets the number of users of the given user type. + * Does not include users that are about to die. + */ + private int getNumberOfUsersOfType(String userType) { + int count = 0; + synchronized (mUsersLock) { + final int size = mUsers.size(); + for (int i = 0; i < size; i++) { + final UserInfo user = mUsers.valueAt(i).info; + if (user.userType.equals(userType) + && !user.guestToRemove + && !mRemovingUserIds.get(user.id) + && !user.preCreated) { + count++; + } + } + } + return count; + } + @Override public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) { - checkManageUsersPermission("check if more managed profiles can be added."); - if (ActivityManager.isLowRamDeviceStatic()) { - return false; - } - if (!mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_MANAGED_USERS)) { + return canAddMoreProfilesToUser(UserManager.USER_TYPE_PROFILE_MANAGED, userId, + allowedToRemoveOne); + } + + /** Returns whether more profiles of the given type can be added to the given parent userId. */ + @Override + public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId, + boolean allowedToRemoveOne) { + checkManageUsersPermission("check if more profiles can be added."); + final UserTypeDetails type = mUserTypes.get(userType); + if (type == null) { return false; } - // Limit number of managed profiles that can be created - final int managedProfilesCount = getProfiles(userId, false).size() - 1; - final int profilesRemovedCount = managedProfilesCount > 0 && allowedToRemoveOne ? 1 : 0; - if (managedProfilesCount - profilesRemovedCount >= getMaxManagedProfiles()) { - return false; + // Managed profiles have their own specific rules. + final boolean isManagedProfile = type.isManagedProfile(); + if (isManagedProfile) { + if (ActivityManager.isLowRamDeviceStatic()) { + return false; + } + if (!mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_MANAGED_USERS)) { + return false; + } } - synchronized(mUsersLock) { + synchronized (mUsersLock) { + // Check if the parent exists and its type is even allowed to have a profile. UserInfo userInfo = getUserInfoLU(userId); if (userInfo == null || !userInfo.canHaveProfile()) { return false; } - int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU() - - profilesRemovedCount; - // We allow creating a managed profile in the special case where there is only one user. - return usersCountAfterRemoving == 1 - || usersCountAfterRemoving < UserManager.getMaxSupportedUsers(); + + // Limit the number of profiles that can be created + final int maxUsersOfType = getMaxUsersOfTypePerParent(type); + if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) { + final int userTypeCount = getProfileIds(userId, userType, false).length; + final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0; + if (userTypeCount - profilesRemovedCount >= maxUsersOfType) { + return false; + } + // Allow creating a managed profile in the special case where there is only one user + if (isManagedProfile) { + int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU() + - profilesRemovedCount; + return usersCountAfterRemoving == 1 + || usersCountAfterRemoving < UserManager.getMaxSupportedUsers(); + } + } } + return true; } @GuardedBy("mUsersLock") @@ -2207,9 +2401,18 @@ public class UserManagerService extends IUserManager.Stub { */ @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) private void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions) { + upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion); + } + + /** + * Version of {@link #upgradeIfNecessaryLP(Bundle)} that takes in the userVersion for testing + * purposes. For non-tests, use {@link #upgradeIfNecessaryLP(Bundle)}. + */ + @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) + @VisibleForTesting + void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion) { Set<Integer> userIdsToWrite = new ArraySet<>(); final int originalVersion = mUserVersion; - int userVersion = mUserVersion; if (userVersion < 1) { // Assign a proper name for the owner, if not initialized correctly before UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM); @@ -2298,6 +2501,44 @@ public class UserManagerService extends IUserManager.Stub { userVersion = 8; } + if (userVersion < 9) { + // Convert from UserInfo flags to UserTypes. Apply FLAG_PROFILE to FLAG_MANAGED_PROFILE. + synchronized (mUsersLock) { + for (int i = 0; i < mUsers.size(); i++) { + UserData userData = mUsers.valueAt(i); + final int flags = userData.info.flags; + if ((flags & UserInfo.FLAG_SYSTEM) != 0) { + if ((flags & UserInfo.FLAG_FULL) != 0) { + userData.info.userType = UserManager.USER_TYPE_FULL_SYSTEM; + } else { + userData.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS; + } + } else { + try { + userData.info.userType = UserInfo.getDefaultUserType(flags); + } catch (IllegalArgumentException e) { + // TODO(b/142482943): What should we do here? Delete user? Crashloop? + throw new IllegalStateException("Cannot upgrade user with flags " + + Integer.toHexString(flags) + " because it doesn't correspond " + + "to a valid user type.", e); + } + } + // OEMs are responsible for their own custom upgrade logic here. + + final UserTypeDetails userTypeDetails = mUserTypes.get(userData.info.userType); + if (userTypeDetails == null) { + throw new IllegalStateException( + "Cannot upgrade user with flags " + Integer.toHexString(flags) + + " because " + userData.info.userType + " isn't defined" + + " on this device!"); + } + userData.info.flags |= userTypeDetails.getDefaultUserInfoFlags(); + userIdsToWrite.add(userData.info.id); + } + } + userVersion = 9; + } + if (userVersion < USER_VERSION) { Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to " + USER_VERSION); @@ -2320,12 +2561,11 @@ public class UserManagerService extends IUserManager.Stub { private void fallbackToSingleUserLP() { int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY; - // In headless system user mode, headless system user is not a full user. - if (!UserManager.isHeadlessSystemUserMode()) { - flags |= UserInfo.FLAG_FULL; - } // Create the system user - UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags); + String systemUserType = UserManager.isHeadlessSystemUserMode() ? + UserManager.USER_TYPE_SYSTEM_HEADLESS : UserManager.USER_TYPE_FULL_SYSTEM; + flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags(); + UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType); UserData userData = putUserInfo(system); mNextSerialNumber = MIN_USER_ID; mUserVersion = USER_VERSION; @@ -2410,6 +2650,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber)); serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); + serializer.attribute(null, ATTR_TYPE, userInfo.userType); serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime)); serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME, Long.toString(userInfo.lastLoggedInTime)); @@ -2570,6 +2811,7 @@ public class UserManagerService extends IUserManager.Stub { UserData readUserLP(int id, InputStream is) throws IOException, XmlPullParserException { int flags = 0; + String userType = null; int serialNumber = id; String name = null; String account = null; @@ -2613,6 +2855,8 @@ public class UserManagerService extends IUserManager.Stub { } serialNumber = readIntAttribute(parser, ATTR_SERIAL_NO, id); flags = readIntAttribute(parser, ATTR_FLAGS, 0); + userType = parser.getAttributeValue(null, ATTR_TYPE); + userType = userType != null ? userType.intern() : null; iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH); creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0); lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0); @@ -2678,7 +2922,7 @@ public class UserManagerService extends IUserManager.Stub { } // Create the UserInfo object that gets passed around - UserInfo userInfo = new UserInfo(id, name, iconPath, flags); + UserInfo userInfo = new UserInfo(id, name, iconPath, flags, userType); userInfo.serialNumber = serialNumber; userInfo.creationTime = creationTime; userInfo.lastLoggedInTime = lastLoggedInTime; @@ -2745,108 +2989,135 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Creates a profile user. Used for actual profiles, like + * {@link UserManager#USER_TYPE_PROFILE_MANAGED}, as well as for + * {@link UserManager#USER_TYPE_FULL_RESTRICTED}. + */ @Override - public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId, - String[] disallowedPackages) { + public UserInfo createProfileForUser(String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { checkManageOrCreateUsersPermission(flags); - return createUserInternal(name, flags, userId, disallowedPackages); + return createUserInternal(name, userType, flags, userId, disallowedPackages); } + /** @see #createProfileForUser */ @Override - public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, - @UserIdInt int userId, String[] disallowedPackages) { + public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { checkManageOrCreateUsersPermission(flags); - return createUserInternalUnchecked(name, flags, userId, /* preCreate= */ false, - disallowedPackages); - } - - @Override - public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { - checkManageOrCreateUsersPermission("Only the system can remove users"); - return removeUserUnchecked(userId); + return createUserInternalUnchecked(name, userType, flags, userId, + /* preCreate= */ false, disallowedPackages); } @Override - public UserInfo createUser(String name, int flags) { + public UserInfo createUser(String name, @NonNull String userType, @UserInfoFlag int flags) { checkManageOrCreateUsersPermission(flags); - return createUserInternal(name, flags, UserHandle.USER_NULL); + return createUserInternal(name, userType, flags, UserHandle.USER_NULL, + /* disallowedPackages= */ null); } @Override - public UserInfo preCreateUser(int flags) { - checkManageOrCreateUsersPermission(flags); + public UserInfo preCreateUser(String userType) { + final UserTypeDetails userTypeDetails = mUserTypes.get(userType); + final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0; - Preconditions.checkArgument(!UserInfo.isManagedProfile(flags), - "cannot pre-create managed profiles"); + checkManageOrCreateUsersPermission(flags); - Slog.i(LOG_TAG, "Pre-creating user with flags " + UserInfo.flagsToString(flags)); + Preconditions.checkArgument(isUserTypeEligibleForPreCreation(userTypeDetails), + "cannot pre-create user of type " + userType); + Slog.i(LOG_TAG, "Pre-creating user of type " + userType); - return createUserInternalUnchecked(/* name= */ null, flags, + return createUserInternalUnchecked(/* name= */ null, userType, 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(@Nullable String name, @UserInfoFlag int flags, - @UserIdInt int parentId, @Nullable String[] disallowedPackages) { - String restriction = ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0) + private UserInfo createUserInternal(@Nullable String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int parentId, + @Nullable String[] disallowedPackages) { + String restriction = (UserManager.isUserTypeManagedProfile(userType)) ? UserManager.DISALLOW_ADD_MANAGED_PROFILE : UserManager.DISALLOW_ADD_USER; if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) { Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled."); return null; } - return createUserInternalUnchecked(name, flags, parentId, /* preCreate= */ false, - disallowedPackages); + return createUserInternalUnchecked(name, userType, flags, parentId, + /* preCreate= */ false, disallowedPackages); } - private UserInfo createUserInternalUnchecked(@Nullable String name, @UserInfoFlag int flags, - @UserIdInt int parentId, boolean preCreate, - @Nullable String[] disallowedPackages) { + private UserInfo createUserInternalUnchecked(@Nullable String name, + @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, + boolean preCreate, @Nullable String[] disallowedPackages) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("createUser-" + flags); try { - return createUserInternalUncheckedNoTracing(name, flags, parentId, preCreate, - disallowedPackages, t); + return createUserInternalUncheckedNoTracing(name, userType, 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 TimingsTraceAndSlog t) { + @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, + boolean preCreate, @Nullable String[] disallowedPackages, + @NonNull TimingsTraceAndSlog t) { + + final UserTypeDetails userTypeDetails = mUserTypes.get(userType); + if (userTypeDetails == null) { + Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType); + return null; + } + userType = userType.intern(); // Now that we know it's valid, we can intern it. + flags |= userTypeDetails.getDefaultUserInfoFlags(); + if (!checkUserTypeConsistency(flags)) { + Slog.e(LOG_TAG, "Cannot add user. Flags (" + Integer.toHexString(flags) + + ") and userTypeDetails (" + userType + ") are inconsistent."); + return null; + } + if ((flags & UserInfo.FLAG_SYSTEM) != 0) { + Slog.e(LOG_TAG, "Cannot add user. Flags (" + Integer.toHexString(flags) + + ") indicated SYSTEM user, which cannot be created."); + return null; + } + synchronized (mUsersLock) { + if (mForceEphemeralUsers) { + flags |= UserInfo.FLAG_EPHEMERAL; + } + } // 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))) { + // TODO(b/142482943): Move this to its own function later. + if (!preCreate + && (parentId < 0 && isUserTypeEligibleForPreCreation(userTypeDetails))) { final UserData preCreatedUserData; synchronized (mUsersLock) { - preCreatedUserData = getPreCreatedUserLU(flags); + preCreatedUserData = getPreCreatedUserLU(userType); } 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)); + final int newFlags = preCreatedUser.flags | flags; + if (!checkUserTypeConsistency(newFlags)) { + Slog.wtf(LOG_TAG, "Cannot reuse pre-created user " + preCreatedUser.id + + " of type " + userType + " because flags are inconsistent. " + + "Flags (" + Integer.toHexString(flags) + "); preCreatedUserFlags ( " + + Integer.toHexString(preCreatedUser.flags) + ")."); + } else { + Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type " + + userType + " and bestowing on it flags " + + UserInfo.flagsToString(flags)); + preCreatedUser.name = name; + preCreatedUser.flags = newFlags; + preCreatedUser.preCreated = false; + preCreatedUser.creationTime = getCreationTime(); + + dispatchUserAddedIntent(preCreatedUser); + writeUserLP(preCreatedUserData); + writeUserListLP(); + return preCreatedUser; } - preCreatedUser.name = name; - preCreatedUser.preCreated = false; - preCreatedUser.creationTime = getCreationTime(); - - dispatchUserAddedIntent(preCreatedUser); - - writeUserLP(preCreatedUserData); - writeUserListLP(); - - return preCreatedUser; } } @@ -2857,10 +3128,11 @@ public class UserManagerService extends IUserManager.Stub { return null; } - 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 boolean isProfile = userTypeDetails.isProfile(); + final boolean isGuest = UserManager.isUserTypeGuest(userType); + final boolean isRestricted = UserManager.isUserTypeRestricted(userType); + final boolean isDemo = UserManager.isUserTypeDemo(userType); + final long ident = Binder.clearCallingIdentity(); UserInfo userInfo; UserData userData; @@ -2874,19 +3146,21 @@ public class UserManagerService extends IUserManager.Stub { } if (parent == null) return null; } - if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) { - Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId); + if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) { + Log.e(LOG_TAG, "Cannot add more users of type " + userType + + ". Maximum number of that type already exists."); 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. - Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached."); + // TODO(b/142482943): Perhaps let the following code apply to restricted users too. + if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) { + Log.e(LOG_TAG, "Cannot add more profiles of type " + userType + + " for user " + parentId); return null; } - // If we're adding a guest and there already exists one, bail. - if (isGuest && !preCreate && findCurrentGuestUser() != null) { - Log.e(LOG_TAG, "Cannot add guest user. Guest user already exists."); + if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) { + // If we're not adding a guest/demo user or a profile and the 'user limit' has + // been reached, cannot add a user. + Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached."); return null; } // In legacy mode, restricted profile's parent can only be the owner user @@ -2908,30 +3182,23 @@ public class UserManagerService extends IUserManager.Stub { } } - if (!isManagedProfile) { - // New users cannot be system, and it's not a profile, so per-force it's FULL. - flags |= UserInfo.FLAG_FULL; - } - userId = getNextAvailableId(); Environment.getUserSystemDirectory(userId).mkdirs(); - boolean ephemeralGuests = areGuestUsersEphemeral(); synchronized (mUsersLock) { - // Add ephemeral flag to guests/users if required. Also inherit it from parent. - if ((isGuest && ephemeralGuests) || mForceEphemeralUsers - || (parent != null && parent.info.isEphemeral())) { + // Inherit ephemeral flag from parent. + if (parent != null && parent.info.isEphemeral()) { flags |= UserInfo.FLAG_EPHEMERAL; } - userInfo = new UserInfo(userId, name, null, flags); + userInfo = new UserInfo(userId, name, null, flags, userType); userInfo.serialNumber = mNextSerialNumber++; userInfo.creationTime = getCreationTime(); userInfo.partial = true; userInfo.preCreated = preCreate; userInfo.lastLoggedInFingerprint = Build.FINGERPRINT; - if (isManagedProfile && parentId != UserHandle.USER_NULL) { - userInfo.profileBadge = getFreeProfileBadgeLU(parentId); + if (userTypeDetails.hasBadge() && parentId != UserHandle.USER_NULL) { + userInfo.profileBadge = getFreeProfileBadgeLU(parentId, userType); } userData = new UserData(); userData.info = userInfo; @@ -2940,7 +3207,7 @@ public class UserManagerService extends IUserManager.Stub { writeUserLP(userData); writeUserListLP(); if (parent != null) { - if (isManagedProfile) { + if (isProfile) { if (parent.info.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { parent.info.profileGroupId = parent.info.id; writeUserLP(parent); @@ -2966,7 +3233,7 @@ public class UserManagerService extends IUserManager.Stub { StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); t.traceEnd(); - final Set<String> installablePackages = + final Set<String> installablePackages = // TODO(b/142482943): use userType mSystemPackageInstaller.getInstallablePackagesForUserType(flags); t.traceBegin("PM.createNewUser"); mPm.createNewUser(userId, installablePackages, disallowedPackages); @@ -2978,6 +3245,7 @@ public class UserManagerService extends IUserManager.Stub { } updateUserIds(); Bundle restrictions = new Bundle(); + // TODO(b/142482943): Generalize this using UserTypeDetails default restrictions. if (isGuest) { synchronized (mGuestRestrictions) { restrictions.putAll(mGuestRestrictions); @@ -3026,6 +3294,22 @@ public class UserManagerService extends IUserManager.Stub { return userInfo; } + /** Checks that the flags do not contain mutually exclusive types/properties. */ + static boolean checkUserTypeConsistency(@UserInfoFlag int flags) { + // Mask to check that flags don't refer to multiple user types. + final int userTypeFlagMask = UserInfo.FLAG_GUEST | UserInfo.FLAG_DEMO + | UserInfo.FLAG_RESTRICTED | UserInfo.FLAG_PROFILE; + return isAtMostOneFlag(flags & userTypeFlagMask) + && isAtMostOneFlag(flags & (UserInfo.FLAG_PROFILE | UserInfo.FLAG_FULL)) + && isAtMostOneFlag(flags & (UserInfo.FLAG_PROFILE | UserInfo.FLAG_SYSTEM)); + } + + /** Returns whether the given flags contains at most one 1. */ + private static boolean isAtMostOneFlag(int flags) { + return (flags & (flags - 1)) == 0; + // If !=0, this means that flags is not a power of 2, and therefore is multiple types. + } + /** Install/uninstall system packages for all users based on their user-type, as applicable. */ boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) { return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade); @@ -3045,39 +3329,23 @@ public class UserManagerService extends IUserManager.Stub { : (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. + * Gets a pre-created user for the given user type. * * <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/143092698): 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)); - } + private @Nullable UserData getPreCreatedUserLU(String userType) { + if (DBG) Slog.d(LOG_TAG, "getPreCreatedUser(): userType= " + userType); 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.preCreated && user.info.userType.equals(userType)) { if (!user.info.isInitialized()) { - Slog.w(LOG_TAG, "found pre-created user for flags " - + "" + UserInfo.flagsToString(flags) + Slog.w(LOG_TAG, "found pre-created user of type " + userType + ", but it's not initialized yet: " + user.info.toFullString()); continue; } @@ -3087,6 +3355,18 @@ public class UserManagerService extends IUserManager.Stub { return null; } + /** + * Returns whether a user with the given userTypeDetails is eligible to be + * {@link UserInfo#preCreated}. + */ + private static boolean isUserTypeEligibleForPreCreation(UserTypeDetails userTypeDetails) { + if (userTypeDetails == null) { + return false; + } + return !userTypeDetails.isProfile() + && !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED); + } + @VisibleForTesting UserData putUserInfo(UserInfo userInfo) { final UserData userData = new UserData(); @@ -3111,7 +3391,7 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo createRestrictedProfile(String name, int parentUserId) { checkManageOrCreateUsersPermission("setupRestrictedProfile"); final UserInfo user = createProfileForUser( - name, UserInfo.FLAG_RESTRICTED, parentUserId, null); + name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null); if (user == null) { return null; } @@ -3217,6 +3497,12 @@ public class UserManagerService extends IUserManager.Stub { return removeUserUnchecked(userId); } + @Override + public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) { + checkManageOrCreateUsersPermission("Only the system can remove users"); + return removeUserUnchecked(userId); + } + private boolean removeUserUnchecked(@UserIdInt int userId) { long ident = Binder.clearCallingIdentity(); try { @@ -3264,6 +3550,7 @@ public class UserManagerService extends IUserManager.Stub { Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e); } + // TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed? if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID && userData.info.isManagedProfile()) { // Send broadcast to notify system that the user removed was a @@ -4035,6 +4322,7 @@ public class UserManagerService extends IUserManager.Stub { pw.print(" <pre-created>"); } pw.println(); + pw.print(" Type: "); pw.println(userInfo.userType); pw.print(" Flags: "); pw.print(userInfo.flags); pw.print(" ("); pw.print(UserInfo.flagsToString(userInfo.flags)); pw.println(")"); pw.print(" State: "); @@ -4119,11 +4407,20 @@ public class UserManagerService extends IUserManager.Stub { pw.print(" Max users: " + UserManager.getMaxSupportedUsers()); pw.println(" (limit reached: " + isUserLimitReached() + ")"); pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers()); - pw.println(" All guests ephemeral: " + areGuestUsersEphemeral()); + pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_guestUserEphemeral)); pw.println(" Is split-system user: " + UserManager.isSplitSystemUser()); pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode()); pw.println(" User version: " + mUserVersion); + // Dump UserTypes + pw.println(); + pw.println(" User types (" + mUserTypes.size() + " types):"); + for (int i = 0; i < mUserTypes.size(); i++) { + pw.println(" " + mUserTypes.keyAt(i) + ": "); + mUserTypes.valueAt(i).dump(pw); + } + // Dump package whitelist pw.println(); mSystemPackageInstaller.dump(pw); @@ -4322,10 +4619,10 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public UserInfo createUserEvenWhenDisallowed(String name, int flags, - String[] disallowedPackages) { - UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL, - /* preCreated= */ false, disallowedPackages); + public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType, + @UserInfoFlag int flags, String[] disallowedPackages) { + UserInfo user = createUserInternalUnchecked(name, userType, flags, + UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages); // Keep this in sync with UserManager.createUser if (user != null && !user.isAdmin() && !user.isDemo()) { setUserRestriction(UserManager.DISALLOW_SMS, true, user.id); @@ -4410,7 +4707,7 @@ public class UserManagerService extends IUserManager.Stub { } synchronized (mUsersLock) { UserInfo callingUserInfo = getUserInfoLU(callingUserId); - if (callingUserInfo == null || callingUserInfo.isManagedProfile()) { + if (callingUserInfo == null || callingUserInfo.isProfile()) { if (throwSecurityException) { throw new SecurityException( debugMsg + " for another profile " @@ -4529,36 +4826,53 @@ public class UserManagerService extends IUserManager.Stub { (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : "")); } + /** @see #getMaxUsersOfTypePerParent(UserTypeDetails) */ @VisibleForTesting - static int getMaxManagedProfiles() { - // Allow overriding max managed profiles on debuggable builds for testing - // of multiple profiles. + int getMaxUsersOfTypePerParent(String userType) { + final UserTypeDetails type = mUserTypes.get(userType); + if (type == null) { + return 0; + } + return getMaxUsersOfTypePerParent(type); + } + + /** + * Returns the maximum number of users allowed for the given userTypeDetails per parent user. + * This is applicable for user types that are {@link UserTypeDetails#isProfile()}. + * If there is no maximum, {@link UserTypeDetails#UNLIMITED_NUMBER_OF_USERS} is returned. + */ + private static int getMaxUsersOfTypePerParent(UserTypeDetails userTypeDetails) { + final int defaultMax = userTypeDetails.getMaxAllowedPerParent(); if (!Build.IS_DEBUGGABLE) { - return MAX_MANAGED_PROFILES; + return defaultMax; } else { - return SystemProperties.getInt("persist.sys.max_profiles", - MAX_MANAGED_PROFILES); + if (userTypeDetails.isManagedProfile()) { + return SystemProperties.getInt("persist.sys.max_profiles", defaultMax); + } } + return defaultMax; } @GuardedBy("mUsersLock") @VisibleForTesting - int getFreeProfileBadgeLU(int parentUserId) { - int maxManagedProfiles = getMaxManagedProfiles(); - boolean[] usedBadges = new boolean[maxManagedProfiles]; + int getFreeProfileBadgeLU(int parentUserId, String userType) { + Set<Integer> usedBadges = new ArraySet<>(); final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i).info; // Check which badge indexes are already used by this profile group. - if (ui.isManagedProfile() + if (ui.userType.equals(userType) && ui.profileGroupId == parentUserId - && !mRemovingUserIds.get(ui.id) - && ui.profileBadge < maxManagedProfiles) { - usedBadges[ui.profileBadge] = true; + && !mRemovingUserIds.get(ui.id)) { + usedBadges.add(ui.profileBadge); } } - for (int i = 0; i < maxManagedProfiles; i++) { - if (!usedBadges[i]) { + int maxUsersOfType = getMaxUsersOfTypePerParent(userType); + if (maxUsersOfType == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) { + maxUsersOfType = Integer.MAX_VALUE; + } + for (int i = 0; i < maxUsersOfType; i++) { + if (!usedBadges.contains(i)) { return i; } } diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 4654ccabeb2b..95197b07deaf 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -411,12 +411,13 @@ class UserSystemPackageInstaller { } // Regardless of the whitelists/blacklists, ensure mandatory packages. result.put("android", - UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK); + UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.FLAG_PROFILE); return result; } /** Converts a user types, as used in SystemConfig, to a UserInfo flag. */ private static int getFlagsFromUserTypes(Iterable<String> userTypes) { + // TODO(b/142482943): Update all this for the new UserTypes. int flags = 0; for (String type : userTypes) { switch (type) { @@ -442,7 +443,7 @@ class UserSystemPackageInstaller { flags |= UserInfo.FLAG_SYSTEM; break; case "PROFILE": - flags |= UserInfo.PROFILE_FLAGS_MASK; + flags |= UserInfo.FLAG_PROFILE; break; default: Slog.w(TAG, "SystemConfig contained an invalid user type: " + type); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9dac03f633dd..f1dbd7b8c28d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9724,13 +9724,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - int userInfoFlags = 0; - if (ephemeral) { - userInfoFlags |= UserInfo.FLAG_EPHEMERAL; - } - if (demo) { - userInfoFlags |= UserInfo.FLAG_DEMO; - } + int userInfoFlags = ephemeral ? UserInfo.FLAG_EPHEMERAL : 0; + String userType = demo ? UserManager.USER_TYPE_FULL_DEMO + : UserManager.USER_TYPE_FULL_SECONDARY; String[] disallowedPackages = null; if (!leaveAllSystemAppsEnabled) { disallowedPackages = mOverlayPackagesProvider.getNonRequiredApps(admin, @@ -9738,7 +9734,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { new String[0]); } UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name, - userInfoFlags, disallowedPackages); + userType, userInfoFlags, disallowedPackages); if (userInfo != null) { user = userInfo.getUserHandle(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java index d6f7e37fcb39..7916bd37060e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java @@ -71,7 +71,7 @@ public class UserLifecycleStressTest { throws IOException, RemoteException, InterruptedException { for (int i = 0; i < NUM_ITERATIONS_STOP_USER; i++) { final UserInfo userInfo = mUserManager.createProfileForUser("TestUser", - UserInfo.FLAG_MANAGED_PROFILE, mActivityManager.getCurrentUser()); + UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser()); assertNotNull(userInfo); try { assertTrue( diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index 8dd896713834..e375aef3b7f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -16,15 +16,15 @@ package com.android.server.pm; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import android.app.ApplicationPackageManager; import android.content.pm.UserInfo; import android.os.Looper; import android.os.UserHandle; import android.os.UserManagerInternal; -import android.util.IconDrawableFactory; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -71,6 +71,7 @@ public class UserManagerServiceCreateProfileTest { removeUsers(); } + /** Tests UMS.getProfileIds() when no specific userType is specified. */ @Test public void testGetProfiles() { // Pretend we have a secondary user with a profile. @@ -93,38 +94,73 @@ public class UserManagerServiceCreateProfileTest { || users.get(1).id == profile.id); } + /** Tests UMS.getProfileIds() when a specific userType is specified. */ + @Test + public void testGetProfileIds_specifyType() { + // Pretend we have a secondary user with a profile. + UserInfo secondaryUser = addUser(); + UserInfo profile = addProfile(secondaryUser); + + // TODO: When there are multiple profiles types, ensure correct output for mixed types. + final String userType1 = USER_TYPE_PROFILE_MANAGED; + + // System user should still have no userType1 profile so getProfileIds should be empty. + int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1, false); + assertEquals("System user should have no managed profiles", 0, users.length); + + // Secondary user should have one userType1 profile, so return just that. + users = mUserManagerService.getProfileIds(secondaryUser.id, userType1, false); + assertEquals("Wrong number of profiles", 1, users.length); + assertEquals("Wrong profile id", profile.id, users[0]); + + // The profile itself is a userType1 profile, so it should return just itself. + users = mUserManagerService.getProfileIds(profile.id, userType1, false); + assertEquals("Wrong number of profiles", 1, users.length); + assertEquals("Wrong profile id", profile.id, users[0]); + } + @Test public void testProfileBadge() { // First profile for system user should get badge 0 assertEquals("First profile isn't given badge index 0", 0, - mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM)); + mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM, + USER_TYPE_PROFILE_MANAGED)); // Pretend we have a secondary user. UserInfo secondaryUser = addUser(); // Check first profile badge for secondary user is also 0. assertEquals("First profile for secondary user isn't given badge index 0", 0, - mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id)); + mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, + USER_TYPE_PROFILE_MANAGED)); // Shouldn't impact the badge for profile in system user assertEquals("First profile isn't given badge index 0 with secondary user", 0, - mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM)); + mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM, + USER_TYPE_PROFILE_MANAGED)); // Pretend a secondary user has a profile. addProfile(secondaryUser); // Shouldn't have impacted the badge for the system user assertEquals("First profile isn't given badge index 0 in secondary user", 0, - mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM)); + mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM, + USER_TYPE_PROFILE_MANAGED)); } @Test public void testProfileBadgeUnique() { List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false); UserInfo system = users.get(0); + int max = mUserManagerService.getMaxUsersOfTypePerParent(USER_TYPE_PROFILE_MANAGED); + if (max < 0) { + // Indicates no max. Instead of infinite, we'll just do 10. + max = 10; + } // Badges should get allocated 0 -> max - for (int i = 0; i < UserManagerService.getMaxManagedProfiles(); ++i) { - int nextBadge = mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM); + for (int i = 0; i < max; ++i) { + int nextBadge = mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM, + USER_TYPE_PROFILE_MANAGED); assertEquals("Wrong badge allocated", i, nextBadge); UserInfo profile = addProfile(system); profile.profileBadge = nextBadge; @@ -140,30 +176,23 @@ public class UserManagerServiceCreateProfileTest { mUserManagerService.addRemovingUserIdLocked(profile.id); // We should reuse the badge from the profile being removed. assertEquals("Badge index not reused while removing a user", 0, - mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id)); + mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, + USER_TYPE_PROFILE_MANAGED)); // Edge case of reuse that only applies if we ever support 3 managed profiles // We should prioritise using lower badge indexes - if (UserManagerService.getMaxManagedProfiles() > 2) { + int max = mUserManagerService.getMaxUsersOfTypePerParent(USER_TYPE_PROFILE_MANAGED); + if (max < 0 || max > 2) { UserInfo profileBadgeOne = addProfile(secondaryUser); profileBadgeOne.profileBadge = 1; // 0 and 2 are free, we should reuse 0 rather than 2. assertEquals("Lower index not used", 0, - mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id)); + mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, + USER_TYPE_PROFILE_MANAGED)); } } @Test - public void testNumberOfBadges() { - assertTrue("Max profiles greater than number of badges", - UserManagerService.MAX_MANAGED_PROFILES - <= IconDrawableFactory.CORP_BADGE_COLORS.length); - assertEquals("Num colors doesn't match number of badge labels", - IconDrawableFactory.CORP_BADGE_COLORS.length, - ApplicationPackageManager.CORP_BADGE_LABEL_RES_ID.length); - } - - @Test public void testCanAddMoreManagedProfiles_removeProfile() { // if device is low-ram or doesn't support managed profiles for some other reason, just // skip the test @@ -171,6 +200,10 @@ public class UserManagerServiceCreateProfileTest { false /* disallow remove */)) { return; } + if (mUserManagerService.getMaxUsersOfTypePerParent(USER_TYPE_PROFILE_MANAGED) < 0) { + // Indicates no limit, so we cannot run this test; + return; + } // GIVEN we've reached the limit of managed profiles possible on the system user while (mUserManagerService.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, @@ -192,6 +225,10 @@ public class UserManagerServiceCreateProfileTest { false /* disallow remove */)) { return; } + if (mUserManagerService.getMaxUsersOfTypePerParent(USER_TYPE_PROFILE_MANAGED) < 0) { + // Indicates no limit, so we cannot run this test; + return; + } // GIVEN we've reached the limit of managed profiles possible on the system user // GIVEN that the profiles are not enabled yet diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 6d5b994a63bb..1e760ccba9f3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -16,11 +16,29 @@ package com.android.server.pm; +import static android.content.pm.UserInfo.FLAG_DEMO; +import static android.content.pm.UserInfo.FLAG_EPHEMERAL; +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_GUEST; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_PROFILE; +import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.content.pm.UserInfo.FLAG_SYSTEM; +import static android.os.UserManager.USER_TYPE_FULL_DEMO; +import static android.os.UserManager.USER_TYPE_FULL_GUEST; +import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED; +import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; +import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.annotation.UserIdInt; import android.content.pm.UserInfo; +import android.content.pm.UserInfo.UserInfoFlag; import android.os.Looper; import android.os.Parcel; import android.os.UserHandle; @@ -121,8 +139,61 @@ public class UserManagerServiceUserInfoTest { assertEquals("A Name", mUserManagerService.getUserInfo(TEST_ID).name); } + /** Test UMS.getUserTypeForUser(). */ + @Test + public void testGetUserTypeForUser() throws Exception { + final String typeSys = mUserManagerService.getUserTypeForUser(UserHandle.USER_SYSTEM); + assertTrue("System user was of invalid type " + typeSys, + typeSys.equals(USER_TYPE_SYSTEM_HEADLESS) || typeSys.equals(USER_TYPE_FULL_SYSTEM)); + + final int testId = 100; + final String typeName = "A type"; + UserInfo userInfo = createUser(testId, 0, typeName); + mUserManagerService.putUserInfo(userInfo); + assertEquals(typeName, mUserManagerService.getUserTypeForUser(testId)); + } + + /** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */ + @Test + public void testUpgradeIfNecessaryLP_9() { + final int versionToTest = 9; + + mUserManagerService.putUserInfo(createUser(100, FLAG_MANAGED_PROFILE, null)); + mUserManagerService.putUserInfo(createUser(101, + FLAG_GUEST | FLAG_EPHEMERAL | FLAG_FULL, null)); + mUserManagerService.putUserInfo(createUser(102, FLAG_RESTRICTED | FLAG_FULL, null)); + mUserManagerService.putUserInfo(createUser(103, FLAG_FULL, null)); + mUserManagerService.putUserInfo(createUser(104, FLAG_SYSTEM, null)); + mUserManagerService.putUserInfo(createUser(105, FLAG_SYSTEM | FLAG_FULL, null)); + mUserManagerService.putUserInfo(createUser(106, FLAG_DEMO | FLAG_FULL, null)); + + mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1); + + assertEquals(USER_TYPE_PROFILE_MANAGED, mUserManagerService.getUserTypeForUser(100)); + assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0); + + assertEquals(USER_TYPE_FULL_GUEST, mUserManagerService.getUserTypeForUser(101)); + + assertEquals(USER_TYPE_FULL_RESTRICTED, mUserManagerService.getUserTypeForUser(102)); + assertTrue((mUserManagerService.getUserInfo(102).flags & FLAG_PROFILE) == 0); + + assertEquals(USER_TYPE_FULL_SECONDARY, mUserManagerService.getUserTypeForUser(103)); + assertTrue((mUserManagerService.getUserInfo(103).flags & FLAG_PROFILE) == 0); + + assertEquals(USER_TYPE_SYSTEM_HEADLESS, mUserManagerService.getUserTypeForUser(104)); + + assertEquals(USER_TYPE_FULL_SYSTEM, mUserManagerService.getUserTypeForUser(105)); + + assertEquals(USER_TYPE_FULL_DEMO, mUserManagerService.getUserTypeForUser(106)); + } + + /** Creates a UserInfo with the given flags and userType. */ + private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) { + return new UserInfo(userId, "A Name", "A path", flags, userType); + } + private UserInfo createUser() { - UserInfo user = new UserInfo(/*id*/ 21, "A Name", "A path", /*flags*/ 0x0ff0ff); + UserInfo user = new UserInfo(/*id*/ 21, "A Name", "A path", /*flags*/ 0x0ff0ff, "A type"); user.serialNumber = 5; user.creationTime = 4L << 32; user.lastLoggedInTime = 5L << 32; @@ -141,6 +212,7 @@ public class UserManagerServiceUserInfoTest { assertEquals("Name not preserved", one.name, two.name); assertEquals("Icon path not preserved", one.iconPath, two.iconPath); assertEquals("Flags not preserved", one.flags, two.flags); + assertEquals("UserType not preserved", one.userType, two.userType); assertEquals("profile group not preserved", one.profileGroupId, two.profileGroupId); assertEquals("restricted profile parent not preseved", one.restrictedProfileParentId, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java new file mode 100644 index 000000000000..7aadd87efb08 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.UserInfo.FLAG_DEMO; +import static android.content.pm.UserInfo.FLAG_EPHEMERAL; +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_GUEST; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_PROFILE; +import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.content.pm.UserInfo.FLAG_SYSTEM; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.os.UserManager; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Tests for {@link UserTypeDetails} and {@link UserTypeFactory}. + * + * <p>Run with: atest UserManagerServiceUserTypeTest + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class UserManagerServiceUserTypeTest { + + @Test + public void testUserTypeBuilder_createUserType() { + UserTypeDetails type = new UserTypeDetails.Builder() + .setName("a.name") + .setEnabled(true) + .setMaxAllowed(21) + .setBaseType(FLAG_FULL) + .setDefaultUserInfoPropertyFlags(FLAG_EPHEMERAL) + .setBadgeLabels(23, 24, 25) + .setBadgeColors(26, 27) + .setIconBadge(28) + .setBadgePlain(29) + .setBadgeNoBackground(30) + .setLabel(31) + .setMaxAllowedPerParent(32) + .setDefaultRestrictions(new ArrayList<>(Arrays.asList("r1", "r2"))) + .createUserTypeDetails(); + + assertEquals("a.name", type.getName()); + assertTrue(type.isEnabled()); + assertEquals(21, type.getMaxAllowed()); + assertEquals(FLAG_FULL | FLAG_EPHEMERAL, type.getDefaultUserInfoFlags()); + assertEquals(28, type.getIconBadge()); + assertEquals(29, type.getBadgePlain()); + assertEquals(30, type.getBadgeNoBackground()); + assertEquals(31, type.getLabel()); + assertEquals(32, type.getMaxAllowedPerParent()); + assertEquals(new ArrayList<>(Arrays.asList("r1", "r2")), type.getDefaultRestrictions()); + + + assertEquals(23, type.getBadgeLabel(0)); + assertEquals(24, type.getBadgeLabel(1)); + assertEquals(25, type.getBadgeLabel(2)); + assertEquals(25, type.getBadgeLabel(3)); + assertEquals(25, type.getBadgeLabel(4)); + assertEquals(Resources.ID_NULL, type.getBadgeLabel(-1)); + + assertEquals(26, type.getBadgeColor(0)); + assertEquals(27, type.getBadgeColor(1)); + assertEquals(27, type.getBadgeColor(2)); + assertEquals(27, type.getBadgeColor(3)); + assertEquals(Resources.ID_NULL, type.getBadgeColor(-100)); + + assertTrue(type.hasBadge()); + } + + @Test + public void testUserTypeBuilder_defaults() { + UserTypeDetails type = new UserTypeDetails.Builder() + .setName("name") // Required (no default allowed) + .setBaseType(FLAG_FULL) // Required (no default allowed) + .createUserTypeDetails(); + + assertTrue(type.isEnabled()); + assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowed()); + assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowedPerParent()); + assertEquals(FLAG_FULL, type.getDefaultUserInfoFlags()); + assertEquals(Resources.ID_NULL, type.getIconBadge()); + assertEquals(Resources.ID_NULL, type.getBadgePlain()); + assertEquals(Resources.ID_NULL, type.getBadgeNoBackground()); + assertEquals(Resources.ID_NULL, type.getBadgeLabel(0)); + assertEquals(Resources.ID_NULL, type.getBadgeColor(0)); + assertEquals(Resources.ID_NULL, type.getLabel()); + assertTrue(type.getDefaultRestrictions().isEmpty()); + + assertFalse(type.hasBadge()); + } + + @Test + public void testUserTypeBuilder_nameIsRequired() { + assertThrows(IllegalArgumentException.class, + () -> new UserTypeDetails.Builder() + .setMaxAllowed(21) + .setBaseType(FLAG_FULL) + .createUserTypeDetails()); + } + + @Test + public void testUserTypeBuilder_baseTypeIsRequired() { + assertThrows(IllegalArgumentException.class, + () -> new UserTypeDetails.Builder() + .setName("name") + .createUserTypeDetails()); + } + + @Test + public void testUserTypeBuilder_colorIsRequiredIfBadged() { + assertThrows(IllegalArgumentException.class, + () -> getMinimalBuilder() + .setIconBadge(1) + .setBadgeLabels(2) + .createUserTypeDetails()); + } + + @Test + public void testUserTypeBuilder_badgeLabelIsRequiredIfBadged() { + assertThrows(IllegalArgumentException.class, + () -> getMinimalBuilder() + .setIconBadge(1) + .setBadgeColors(2) + .createUserTypeDetails()); + } + + @Test + public void testCheckUserTypeConsistency() { + assertTrue(UserManagerService.checkUserTypeConsistency(FLAG_GUEST)); + assertTrue(UserManagerService.checkUserTypeConsistency(FLAG_GUEST | FLAG_EPHEMERAL)); + assertTrue(UserManagerService.checkUserTypeConsistency(FLAG_PROFILE)); + + assertFalse(UserManagerService.checkUserTypeConsistency(FLAG_DEMO | FLAG_RESTRICTED)); + assertFalse(UserManagerService.checkUserTypeConsistency(FLAG_PROFILE | FLAG_SYSTEM)); + assertFalse(UserManagerService.checkUserTypeConsistency(FLAG_PROFILE | FLAG_FULL)); + } + + @Test + public void testGetDefaultUserType() { + // Simple example. + assertEquals(UserManager.USER_TYPE_FULL_RESTRICTED, + UserInfo.getDefaultUserType(FLAG_RESTRICTED)); + + // Type plus a non-type flag. + assertEquals(UserManager.USER_TYPE_FULL_GUEST, + UserInfo.getDefaultUserType(FLAG_GUEST | FLAG_EPHEMERAL)); + + // Two types, which is illegal. + assertThrows(IllegalArgumentException.class, + () -> UserInfo.getDefaultUserType(FLAG_MANAGED_PROFILE | FLAG_GUEST)); + + // No type, which defaults to {@link UserManager#USER_TYPE_FULL_SECONDARY}. + assertEquals(UserManager.USER_TYPE_FULL_SECONDARY, + UserInfo.getDefaultUserType(FLAG_EPHEMERAL)); + } + + /** Returns a minimal {@link UserTypeDetails.Builder} that can legitimately be created. */ + private UserTypeDetails.Builder getMinimalBuilder() { + return new UserTypeDetails.Builder().setName("name").setBaseType(FLAG_FULL); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index e9edba58a3dd..d07192768182 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -23,6 +24,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -196,6 +198,62 @@ public class UserManagerTest extends AndroidTestCase { } } + /** Tests creating a FULL user via specifying userType. */ + @MediumTest + public void testCreateUserViaTypes() throws Exception { + createUserWithTypeAndCheckFlags(UserManager.USER_TYPE_FULL_GUEST, + UserInfo.FLAG_GUEST | UserInfo.FLAG_FULL); + + createUserWithTypeAndCheckFlags(UserManager.USER_TYPE_FULL_DEMO, + UserInfo.FLAG_DEMO | UserInfo.FLAG_FULL); + + createUserWithTypeAndCheckFlags(UserManager.USER_TYPE_FULL_SECONDARY, + UserInfo.FLAG_FULL); + } + + /** Tests creating a FULL user via specifying user flags. */ + @MediumTest + public void testCreateUserViaFlags() throws Exception { + createUserWithFlagsAndCheckType(UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST, + UserInfo.FLAG_FULL); + + createUserWithFlagsAndCheckType(0, UserManager.USER_TYPE_FULL_SECONDARY, + UserInfo.FLAG_FULL); + + createUserWithFlagsAndCheckType(UserInfo.FLAG_FULL, UserManager.USER_TYPE_FULL_SECONDARY, + 0); + + createUserWithFlagsAndCheckType(UserInfo.FLAG_DEMO, UserManager.USER_TYPE_FULL_DEMO, + UserInfo.FLAG_FULL); + } + + /** Creates a user of the given user type and checks that the result has the requiredFlags. */ + private void createUserWithTypeAndCheckFlags(String userType, + @UserIdInt int requiredFlags) { + final UserInfo userInfo = createUser("Name", userType, 0); + assertEquals("Wrong user type", userType, userInfo.userType); + assertEquals( + "Flags " + userInfo.flags + " did not contain expected " + requiredFlags, + requiredFlags, userInfo.flags & requiredFlags); + removeUser(userInfo.id); + } + + /** + * Creates a user of the given flags and checks that the result is of the expectedUserType type + * and that it has the expected flags (including both flags and any additionalRequiredFlags). + */ + private void createUserWithFlagsAndCheckType(@UserIdInt int flags, String expectedUserType, + @UserIdInt int additionalRequiredFlags) { + final UserInfo userInfo = createUser("Name", flags); + assertEquals("Wrong user type", expectedUserType, userInfo.userType); + additionalRequiredFlags |= flags; + assertEquals( + "Flags " + userInfo.flags + " did not contain expected " + additionalRequiredFlags, + additionalRequiredFlags, userInfo.flags & additionalRequiredFlags); + removeUser(userInfo.id); + } + + @MediumTest public void testAddGuest() throws Exception { UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST); @@ -234,7 +292,7 @@ public class UserManagerTest extends AndroidTestCase { final int primaryUserId = mUserManager.getPrimaryUser().id; UserInfo userInfo = createProfileForUser("Profile", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); assertNotNull(userInfo); assertNull(mUserManager.getProfileParent(primaryUserId)); UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); @@ -244,17 +302,61 @@ public class UserManagerTest extends AndroidTestCase { assertNull(mUserManager.getProfileParent(primaryUserId)); } + /** Test that UserManager returns the correct badge information for a managed profile. */ + @MediumTest + public void testProfileTypeInformation() throws Exception { + final UserTypeDetails userTypeDetails = + UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED); + assertNotNull("No " + UserManager.USER_TYPE_PROFILE_MANAGED + " type on device", + userTypeDetails); + assertEquals(UserManager.USER_TYPE_PROFILE_MANAGED, userTypeDetails.getName()); + + final int primaryUserId = mUserManager.getPrimaryUser().id; + UserInfo userInfo = createProfileForUser("Managed", + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); + assertNotNull(userInfo); + final int userId = userInfo.id; + final UserHandle userHandle = new UserHandle(userId); + + assertEquals(userTypeDetails.hasBadge(), + mUserManager.hasBadge(userId)); + assertEquals(userTypeDetails.getIconBadge(), + mUserManager.getUserIconBadgeResId(userId)); + assertEquals(userTypeDetails.getBadgePlain(), + mUserManager.getUserBadgeResId(userId)); + assertEquals(userTypeDetails.getBadgeNoBackground(), + mUserManager.getUserBadgeNoBackgroundResId(userId)); + assertEquals(userTypeDetails.isProfile(), + mUserManager.isProfile(userId)); + assertEquals(userTypeDetails.getName(), + mUserManager.getUserTypeForUser(userHandle)); + + final int badgeIndex = userInfo.profileBadge; + assertEquals( + Resources.getSystem().getColor(userTypeDetails.getBadgeColor(badgeIndex), null), + mUserManager.getUserBadgeColor(userId)); + assertEquals( + Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test"), + mUserManager.getBadgedLabelForUser("Test", userHandle)); + } + // Make sure only one managed profile can be created @MediumTest public void testAddManagedProfile() throws Exception { final int primaryUserId = mUserManager.getPrimaryUser().id; UserInfo userInfo1 = createProfileForUser("Managed 1", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); UserInfo userInfo2 = createProfileForUser("Managed 2", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); assertNotNull(userInfo1); assertNull(userInfo2); + + assertEquals(userInfo1.userType, UserManager.USER_TYPE_PROFILE_MANAGED); + int requiredFlags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_PROFILE; + assertEquals("Wrong flags " + userInfo1.flags, requiredFlags, + userInfo1.flags & requiredFlags); + // Verify that current user is not a managed profile assertFalse(mUserManager.isManagedProfile()); } @@ -264,7 +366,7 @@ public class UserManagerTest extends AndroidTestCase { public void testAddManagedProfile_withDisallowedPackages() throws Exception { final int primaryUserId = mUserManager.getPrimaryUser().id; UserInfo userInfo1 = createProfileForUser("Managed1", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); // Verify that the packagesToVerify are installed by default. for (String pkg : PACKAGES) { assertTrue("Package should be installed in managed profile: " + pkg, @@ -273,7 +375,7 @@ public class UserManagerTest extends AndroidTestCase { removeUser(userInfo1.id); UserInfo userInfo2 = createProfileForUser("Managed2", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId, PACKAGES); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES); // Verify that the packagesToVerify are not installed by default. for (String pkg : PACKAGES) { assertFalse("Package should not be installed in managed profile when disallowed: " @@ -287,7 +389,7 @@ public class UserManagerTest extends AndroidTestCase { public void testAddManagedProfile_disallowedPackagesInstalledLater() throws Exception { final int primaryUserId = mUserManager.getPrimaryUser().id; UserInfo userInfo = createProfileForUser("Managed", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId, PACKAGES); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES); // Verify that the packagesToVerify are not installed by default. for (String pkg : PACKAGES) { assertFalse("Package should not be installed in managed profile when disallowed: " @@ -326,7 +428,7 @@ public class UserManagerTest extends AndroidTestCase { primaryUserHandle); try { UserInfo userInfo = createProfileForUser("Managed", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); assertNull(userInfo); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, @@ -343,7 +445,7 @@ public class UserManagerTest extends AndroidTestCase { primaryUserHandle); try { UserInfo userInfo = createProfileEvenWhenDisallowedForUser("Managed", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); assertNotNull(userInfo); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, @@ -359,7 +461,7 @@ public class UserManagerTest extends AndroidTestCase { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle); try { UserInfo userInfo = createProfileForUser("Managed", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); assertNotNull(userInfo); } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, @@ -396,7 +498,7 @@ public class UserManagerTest extends AndroidTestCase { final int primaryUserId = mUserManager.getPrimaryUser().id; final long startTime = System.currentTimeMillis(); UserInfo profile = createProfileForUser("Managed 1", - UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId); final long endTime = System.currentTimeMillis(); assertNotNull(profile); if (System.currentTimeMillis() > EPOCH_PLUS_30_YEARS) { @@ -663,24 +765,32 @@ public class UserManagerTest extends AndroidTestCase { return user; } - private UserInfo createProfileForUser(String name, int flags, int userHandle) { - return createProfileForUser(name, flags, userHandle, null); + private UserInfo createUser(String name, String userType, int flags) { + UserInfo user = mUserManager.createUser(name, userType, flags); + if (user != null) { + usersToRemove.add(user.id); + } + return user; + } + + private UserInfo createProfileForUser(String name, String userType, int userHandle) { + return createProfileForUser(name, userType, userHandle, null); } - private UserInfo createProfileForUser(String name, int flags, int userHandle, + private UserInfo createProfileForUser(String name, String userType, int userHandle, String[] disallowedPackages) { UserInfo profile = mUserManager.createProfileForUser( - name, flags, userHandle, disallowedPackages); + name, userType, 0, userHandle, disallowedPackages); if (profile != null) { usersToRemove.add(profile.id); } return profile; } - private UserInfo createProfileEvenWhenDisallowedForUser(String name, int flags, + private UserInfo createProfileEvenWhenDisallowedForUser(String name, String userType, int userHandle) { UserInfo profile = mUserManager.createProfileForUserEvenWhenDisallowed( - name, flags, userHandle, null); + name, userType, 0, userHandle, null); if (profile != null) { usersToRemove.add(profile.id); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java index f0b0328ff7d4..f492932a0d77 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -147,7 +147,7 @@ public class UserSystemPackageInstallerTest { final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap(); expectedOutput.put("com.android.package1", - UserInfo.PROFILE_FLAGS_MASK | FLAG_SYSTEM | FLAG_GUEST); + UserInfo.FLAG_PROFILE | FLAG_SYSTEM | FLAG_GUEST); expectedOutput.put("com.android.package2", UserInfo.FLAG_MANAGED_PROFILE); @@ -376,9 +376,9 @@ public class UserSystemPackageInstallerTest { /** Sets the whitelist mode to the desired value via adb's setprop. */ private void setUserTypePackageWhitelistMode(int mode) { - UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); try { - String result = mUiDevice.executeShellCommand(String.format("setprop %s %d", + String result = uiDevice.executeShellCommand(String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode)); assertFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result, result != null && result.contains("Failed")); @@ -390,7 +390,7 @@ public class UserSystemPackageInstallerTest { private ArrayMap<String, Integer> getNewPackageToWhitelistedFlagsMap() { final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); // "android" is always treated as whitelisted, regardless of the xml file. - pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK); + pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.FLAG_PROFILE); return pkgFlagMap; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserTests.java b/services/tests/servicestests/src/com/android/server/pm/UserTests.java new file mode 100644 index 000000000000..525382d50d72 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserTests.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + UserDataPreparerTest.class, + UserLifecycleStressTest.class, + UserManagerServiceCreateProfileTest.class, + UserManagerServiceIdRecyclingTest.class, + UserManagerServiceTest.class, + UserManagerServiceUserInfoTest.class, + UserManagerServiceUserTypeTest.class, + UserManagerTest.class, + UserRestrictionsUtilsTest.class, + UserSystemPackageInstallerTest.class, +}) +public class UserTests { +} + |