Merge "Introduce user types"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index e74e4a9..278a786 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 @@
/** 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 5661347..0113f69 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -92,7 +92,6 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
@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 @@
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 1e88ce7..42d64d8 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.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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 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 @@
FLAG_EPHEMERAL,
FLAG_DEMO,
FLAG_FULL,
- FLAG_SYSTEM
+ FLAG_SYSTEM,
+ FLAG_PROFILE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
@@ -170,6 +181,13 @@
@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 @@
@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 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 @@
@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);
+ }
+
+ public boolean isProfile() {
+ return (flags & FLAG_PROFILE) != 0;
}
@UnsupportedAppUsage
public boolean isManagedProfile() {
- return isManagedProfile(flags);
- }
-
- /**
- * Checks if the flag denotes a managed profile.
- */
- public static boolean isManagedProfile(@UserInfoFlag int flags) {
- return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
+ return UserManager.isUserTypeManagedProfile(userType);
}
@UnsupportedAppUsage
@@ -271,7 +331,7 @@
}
public boolean isDemo() {
- return (flags & FLAG_DEMO) == FLAG_DEMO;
+ return UserManager.isUserTypeDemo(userType);
}
public boolean isFull() {
@@ -304,7 +364,7 @@
// Don't support switching to an ephemeral user with removal in progress.
return false;
}
- return !isManagedProfile();
+ return !isProfile();
}
/**
@@ -316,9 +376,10 @@
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 @@
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 @@
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 String toFullString() {
return "UserInfo[id=" + id
+ ", name=" + name
+ + ", type=" + userType
+ ", flags=" + flagsToString(flags)
+ (preCreated ? " (pre-created)" : "")
+ (partial ? " (partial)" : "")
@@ -387,6 +451,7 @@
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 @@
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 e8cc73f..40048d9 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -35,77 +35,84 @@
/*
* 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 b096049..ed21b72 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -18,6 +18,8 @@
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 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
/**
* 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 @@
}
/**
- * 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 @@
/**
* 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 @@
}
/**
- * 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 @@
*
* @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 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 @@
/**
* 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 @@
*
* @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 @@
* 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 @@
* @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 @@
}
/**
+ * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
* @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 d90b65e..d86ebf3 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -26,8 +26,6 @@
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 @@
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 @@
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 8501eb5..e652e17 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -106,9 +106,11 @@
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 0000000..5fc3ba1
--- /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 0000000..43bbab1
--- /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 22b2314..1bc46a7 100644
--- a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java
@@ -19,6 +19,7 @@
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 @@
@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 274696b..69487d5 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.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import android.os.UserManager;
import com.android.settingslib.R;
@@ -176,8 +177,8 @@
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 e5f8b49..9a7cb3f 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -143,8 +143,8 @@
* <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 @@
/**
* 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 @@
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 4361676..31ceb38 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 @@
// 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 2b6c347..bd95667 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 @@
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 525d357..232374c 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 @@
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;
+ newUserType = UserManager.USER_TYPE_FULL_RESTRICTED;
+ } else if ("--guest".equals(opt)) {
+ newUserType = UserManager.USER_TYPE_FULL_GUEST;
+ } else if ("--demo".equals(opt)) {
+ newUserType = UserManager.USER_TYPE_FULL_DEMO;
} else if ("--ephemeral".equals(opt)) {
flags |= UserInfo.FLAG_EPHEMERAL;
- } else if ("--guest".equals(opt)) {
- flags |= UserInfo.FLAG_GUEST;
- } else if ("--demo".equals(opt)) {
- flags |= UserInfo.FLAG_DEMO;
} 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 @@
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 @@
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 5c9b9c9..8ddfad9 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_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.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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
/** 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 @@
/**
* 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 @@
if (profile.partial) {
continue;
}
+ if (userType != null && !userType.equals(profile.userType)) {
+ continue;
+ }
result.add(profile.id);
}
return result;
@@ -1116,6 +1144,45 @@
}
}
+ /**
+ * 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 @@
}
@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 @@
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 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;
}
- if (!mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_MANAGED_USERS)) {
- 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;
+ }
}
- // 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;
- }
- 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 @@
*/
@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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
// 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 @@
}
}
+ /**
+ * 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, @NonNull String userType,
+ @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) {
+ checkManageOrCreateUsersPermission(flags);
+ return createUserInternalUnchecked(name, userType, flags, userId,
+ /* preCreate= */ false, disallowedPackages);
}
@Override
- public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
- @UserIdInt int userId, String[] disallowedPackages) {
+ public UserInfo createUser(String name, @NonNull String userType, @UserInfoFlag int flags) {
checkManageOrCreateUsersPermission(flags);
- return createUserInternalUnchecked(name, flags, userId, /* preCreate= */ false,
- disallowedPackages);
+ return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
+ /* disallowedPackages= */ null);
}
@Override
- public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
- checkManageOrCreateUsersPermission("Only the system can remove users");
- return removeUserUnchecked(userId);
- }
+ public UserInfo preCreateUser(String userType) {
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+ final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0;
- @Override
- public UserInfo createUser(String name, int flags) {
- checkManageOrCreateUsersPermission(flags);
- return createUserInternal(name, flags, UserHandle.USER_NULL);
- }
-
- @Override
- public UserInfo preCreateUser(int flags) {
checkManageOrCreateUsersPermission(flags);
- Preconditions.checkArgument(!UserInfo.isManagedProfile(flags),
- "cannot pre-create managed profiles");
+ Preconditions.checkArgument(isUserTypeEligibleForPreCreation(userTypeDetails),
+ "cannot pre-create user of type " + userType);
+ Slog.i(LOG_TAG, "Pre-creating user of type " + userType);
- Slog.i(LOG_TAG, "Pre-creating user with flags " + UserInfo.flagsToString(flags));
-
- 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 @@
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,21 +3146,23 @@
}
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.
+ // 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 (!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;
}
- // 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.");
- return null;
- }
// In legacy mode, restricted profile's parent can only be the owner user
if (isRestricted && !UserManager.isSplitSystemUser()
&& (parentId != UserHandle.USER_SYSTEM)) {
@@ -2908,30 +3182,23 @@
}
}
- 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 @@
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 @@
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 @@
}
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 @@
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 @@
: (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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
}
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 @@
(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.
- if (!Build.IS_DEBUGGABLE) {
- return MAX_MANAGED_PROFILES;
- } else {
- return SystemProperties.getInt("persist.sys.max_profiles",
- MAX_MANAGED_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 defaultMax;
+ } else {
+ 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 4654cca..95197b0 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 @@
}
// 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 @@
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 9dac03f..f1dbd7b 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 @@
}
}
- 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 @@
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 d6f7e37..7916bd3 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 @@
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 8dd8967..e375aef 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 @@
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 @@
|| 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 @@
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 @@
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 @@
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 6d5b994..1e760cc 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 @@
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 @@
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 0000000..7aadd87
--- /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 e9edba5..d071927 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.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 @@
}
}
+ /** 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 @@
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 @@
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 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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, int flags, int userHandle,
+ private UserInfo createProfileForUser(String name, String userType, int userHandle) {
+ return createProfileForUser(name, userType, userHandle, null);
+ }
+
+ 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 f0b0328..f492932 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 @@
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 @@
/** 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 @@
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 0000000..525382d
--- /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 {
+}
+