Loading core/res/res/xml/config_user_types.xml +10 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ The following example modifies two AOSP user types (the FULL user android.os.use and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROFILE user type (com.example.profilename): <user-types> <user-types version="0"> <full-type name="android.os.usertype.full.SECONDARY" > <default-restrictions no_sms="true" /> </full-type> Loading @@ -65,6 +65,11 @@ and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROF <profile-type name="com.example.profilename" max-allowed-per-parent="2" /> <change-user-type from="android.os.usertype.profile.MANAGED" to="com.example.profilename" whenVersionLeq="1" /> </user-types> Mandatory attributes: Loading Loading @@ -93,6 +98,10 @@ If this file is updated, the properties of any pre-existing user types will be u Note, however, that default-restrictions refers to the restrictions applied at the time of user creation; therefore, the active restrictions of any pre-existing users will not be updated. The 'change-user-type' tag should be used in conjunction with the 'version' property of 'user-types'. It defines a type change for all pre-existing users of 'from' type to the new 'to' type, if the former 'user-type's version of device is less than or equal to 'whenVersionLeq'. --> <user-types> </user-types> services/core/java/com/android/server/pm/UserManagerService.java +120 −4 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileDescriptor; Loading Loading @@ -175,6 +174,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_CONVERTED_FROM_PRE_CREATED = "convertedFromPreCreated"; private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove"; private static final String ATTR_USER_VERSION = "version"; private static final String ATTR_USER_TYPE_VERSION = "userTypeConfigVersion"; private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId"; private static final String ATTR_PROFILE_BADGE = "profileBadge"; private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId"; Loading Loading @@ -422,6 +422,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mPackagesLock") private int mNextSerialNumber; private int mUserVersion = 0; private int mUserTypeVersion = 0; private IAppOpsService mAppOpsService; Loading Loading @@ -2565,6 +2566,8 @@ public class UserManagerService extends IUserManager.Stub { parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber); mUserVersion = parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion); mUserTypeVersion = parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); } // Pre-O global user restriction were stored as a single bundle (as opposed to per-user Loading Loading @@ -2627,7 +2630,7 @@ public class UserManagerService extends IUserManager.Stub { */ @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) private void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions) { upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion); upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion, mUserTypeVersion); } /** Loading @@ -2636,9 +2639,11 @@ public class UserManagerService extends IUserManager.Stub { */ @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) @VisibleForTesting void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion) { void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion, int userTypeVersion) { Set<Integer> userIdsToWrite = new ArraySet<>(); final int originalVersion = mUserVersion; final int originalUserTypeVersion = mUserTypeVersion; if (userVersion < 1) { // Assign a proper name for the owner, if not initialized correctly before UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM); Loading Loading @@ -2771,13 +2776,24 @@ public class UserManagerService extends IUserManager.Stub { userVersion = 9; } // Done with userVersion changes, moving on to deal with userTypeVersion upgrades // Upgrade from previous user type to a new user type final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion(); if (newUserTypeVersion > userTypeVersion) { synchronized (mUsersLock) { upgradeUserTypesLU(UserTypeFactory.getUserTypeUpgrades(), mUserTypes, userTypeVersion, userIdsToWrite); } } if (userVersion < USER_VERSION) { Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to " + USER_VERSION); } else { mUserVersion = userVersion; mUserTypeVersion = newUserTypeVersion; if (originalVersion < mUserVersion) { if (originalVersion < mUserVersion || originalUserTypeVersion < mUserTypeVersion) { for (int userId : userIdsToWrite) { UserData userData = getUserDataNoChecks(userId); if (userData != null) { Loading @@ -2801,6 +2817,7 @@ public class UserManagerService extends IUserManager.Stub { UserData userData = putUserInfo(system); mNextSerialNumber = MIN_USER_ID; mUserVersion = USER_VERSION; mUserTypeVersion = UserTypeFactory.getUserTypeVersion(); Bundle restrictions = new Bundle(); try { Loading Loading @@ -2991,6 +3008,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.startTag(null, TAG_USERS); serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber); serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion); serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); serializer.startTag(null, TAG_GUEST_RESTRICTIONS); synchronized (mGuestRestrictions) { Loading Loading @@ -4957,6 +4975,7 @@ public class UserManagerService extends IUserManager.Stub { // Dump UserTypes pw.println(); pw.println("User types version: " + mUserTypeVersion); pw.println("User types (" + mUserTypes.size() + " types):"); for (int i = 0; i < mUserTypes.size(); i++) { pw.println(" " + mUserTypes.keyAt(i) + ": "); Loading Loading @@ -5447,6 +5466,9 @@ public class UserManagerService extends IUserManager.Stub { * 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. * Under certain circumstances (such as after a change-user-type) the max value can actually * be exceeded: this is allowed in order to keep the device in a usable state. * An error is logged in {@link UserManagerService#upgradeProfileToTypeLU} */ private static int getMaxUsersOfTypePerParent(UserTypeDetails userTypeDetails) { final int defaultMax = userTypeDetails.getMaxAllowedPerParent(); Loading Loading @@ -5534,4 +5556,98 @@ public class UserManagerService extends IUserManager.Stub { } return mDevicePolicyManagerInternal; } @GuardedBy("mUsersLock") @VisibleForTesting void upgradeUserTypesLU(@NonNull List<UserTypeFactory.UserTypeUpgrade> upgradeOps, @NonNull ArrayMap<String, UserTypeDetails> userTypes, final int formerUserTypeVersion, @NonNull Set<Integer> userIdsToWrite) { for (UserTypeFactory.UserTypeUpgrade userTypeUpgrade : upgradeOps) { if (DBG) { Slog.i(LOG_TAG, "Upgrade: " + userTypeUpgrade.getFromType() + " to: " + userTypeUpgrade.getToType() + " maxVersion: " + userTypeUpgrade.getUpToVersion()); } // upgrade user type if version up to getUpToVersion() if (formerUserTypeVersion <= userTypeUpgrade.getUpToVersion()) { for (int i = 0; i < mUsers.size(); i++) { UserData userData = mUsers.valueAt(i); if (userTypeUpgrade.getFromType().equals(userData.info.userType)) { final UserTypeDetails newUserType = userTypes.get( userTypeUpgrade.getToType()); if (newUserType == null) { throw new IllegalStateException( "Upgrade destination user type not defined: " + userTypeUpgrade.getToType()); } upgradeProfileToTypeLU(userData.info, newUserType); userIdsToWrite.add(userData.info.id); } } } } } /** * Changes the user type of a profile to a new user type. * @param userInfo The user to be updated. * @param newUserType The new user type. */ @GuardedBy("mUsersLock") @VisibleForTesting void upgradeProfileToTypeLU(@NonNull UserInfo userInfo, @NonNull UserTypeDetails newUserType) { Slog.i(LOG_TAG, "Upgrading user " + userInfo.id + " from " + userInfo.userType + " to " + newUserType.getName()); if (!userInfo.isProfile()) { throw new IllegalStateException( "Can only upgrade profile types. " + userInfo.userType + " is not a profile type."); } // Exceeded maximum profiles for parent user: log error, but allow upgrade if (!canAddMoreProfilesToUser(newUserType.getName(), userInfo.profileGroupId, false)) { Slog.w(LOG_TAG, "Exceeded maximum profiles of type " + newUserType.getName() + " for user " + userInfo.id + ". Maximum allowed= " + newUserType.getMaxAllowedPerParent()); } final UserTypeDetails oldUserType = mUserTypes.get(userInfo.userType); final int oldFlags; if (oldUserType != null) { oldFlags = oldUserType.getDefaultUserInfoFlags(); } else { // if oldUserType is missing from config_user_types.xml -> can only assume FLAG_PROFILE oldFlags = UserInfo.FLAG_PROFILE; } //convert userData to newUserType userInfo.userType = newUserType.getName(); // remove old default flags and add newUserType's default flags userInfo.flags = newUserType.getDefaultUserInfoFlags() | (userInfo.flags ^ oldFlags); // merge existing base restrictions with the new type's default restrictions synchronized (mRestrictionsLock) { if (!UserRestrictionsUtils.isEmpty(newUserType.getDefaultRestrictions())) { final Bundle newRestrictions = UserRestrictionsUtils.clone( mBaseUserRestrictions.getRestrictions(userInfo.id)); UserRestrictionsUtils.merge(newRestrictions, newUserType.getDefaultRestrictions()); updateUserRestrictionsInternalLR(newRestrictions, userInfo.id); if (DBG) { Slog.i(LOG_TAG, "Updated user " + userInfo.id + " restrictions to " + newRestrictions); } } } // re-compute badge index userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType); } } services/core/java/com/android/server/pm/UserTypeDetails.java +3 −0 Original line number Diff line number Diff line Loading @@ -174,6 +174,9 @@ public final class UserTypeDetails { /** * Returns the maximum number of this user type allowed per parent (for user types, like * profiles, that have parents). * Under certain circumstances (such as after a change-user-type) the max value can actually * be exceeded: this is allowed in order to keep the device in a usable state. * An error is logged in {@link UserManagerService#upgradeProfileToTypeLU} * <p>Returns {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. */ public int getMaxAllowedPerParent() { Loading services/core/java/com/android/server/pm/UserTypeFactory.java +148 −8 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; /** Loading @@ -73,14 +74,7 @@ public final class UserTypeFactory { * @return mapping from the name of each user type to its {@link UserTypeDetails} object */ public static ArrayMap<String, UserTypeDetails> getUserTypes() { final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged()); builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem()); builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary()); builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest()); builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo()); builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted()); builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); final ArrayMap<String, UserTypeDetails.Builder> builders = getDefaultBuilders(); try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { Loading @@ -94,6 +88,20 @@ public final class UserTypeFactory { return types; } private static ArrayMap<String, UserTypeDetails.Builder> getDefaultBuilders() { final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged()); builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem()); builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary()); builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest()); builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo()); builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted()); builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); return builders; } /** * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED} * configuration. Loading Loading @@ -232,6 +240,10 @@ public final class UserTypeFactory { isProfile = true; } else if ("full-type".equals(elementName)) { isProfile = false; } else if ("change-user-type".equals(elementName)) { // parsed in parseUserUpgrades XmlUtils.skipCurrentTag(parser); continue; } else { Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in " + parser.getPositionDescription()); Loading Loading @@ -387,4 +399,132 @@ public final class UserTypeFactory { } fcn.accept(result); } /** * Returns the user type version of the config XML file. * @return user type version defined in XML file, 0 if none. */ public static int getUserTypeVersion() { try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { return getUserTypeVersion(parser); } } @VisibleForTesting static int getUserTypeVersion(XmlResourceParser parser) { int version = 0; try { XmlUtils.beginDocument(parser, "user-types"); String versionValue = parser.getAttributeValue(null, "version"); if (versionValue != null) { try { version = Integer.parseInt(versionValue); } catch (NumberFormatException e) { Slog.e(LOG_TAG, "Cannot parse value of '" + versionValue + "' for version in " + parser.getPositionDescription(), e); throw e; } } } catch (XmlPullParserException | IOException e) { Slog.w(LOG_TAG, "Cannot read user type configuration file.", e); } return version; } /** * Obtains the user type upgrades for this device. * @return The list of user type upgrades. */ public static List<UserTypeUpgrade> getUserTypeUpgrades() { final List<UserTypeUpgrade> userUpgrades; try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { userUpgrades = parseUserUpgrades(getDefaultBuilders(), parser); } return userUpgrades; } @VisibleForTesting static List<UserTypeUpgrade> parseUserUpgrades( ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser) { final List<UserTypeUpgrade> userUpgrades = new ArrayList<>(); try { XmlUtils.beginDocument(parser, "user-types"); for (XmlUtils.nextElement(parser); parser.getEventType() != XmlResourceParser.END_DOCUMENT; XmlUtils.nextElement(parser)) { final String elementName = parser.getName(); if ("change-user-type".equals(elementName)) { final String fromType = parser.getAttributeValue(null, "from"); final String toType = parser.getAttributeValue(null, "to"); // Check that the base type doesn't change. // Currently, only the base type of PROFILE is supported. validateUserTypeIsProfile(fromType, builders); validateUserTypeIsProfile(toType, builders); final int maxVersionToConvert; try { maxVersionToConvert = Integer.parseInt( parser.getAttributeValue(null, "whenVersionLeq")); } catch (NumberFormatException e) { Slog.e(LOG_TAG, "Cannot parse value of whenVersionLeq in " + parser.getPositionDescription(), e); throw e; } UserTypeUpgrade userTypeUpgrade = new UserTypeUpgrade(fromType, toType, maxVersionToConvert); userUpgrades.add(userTypeUpgrade); continue; } else { XmlUtils.skipCurrentTag(parser); continue; } } } catch (XmlPullParserException | IOException e) { Slog.w(LOG_TAG, "Cannot read user type configuration file.", e); } return userUpgrades; } private static void validateUserTypeIsProfile(String userType, ArrayMap<String, UserTypeDetails.Builder> builders) { UserTypeDetails.Builder builder = builders.get(userType); if (builder != null && builder.getBaseType() != FLAG_PROFILE) { throw new IllegalArgumentException("Illegal upgrade of user type " + userType + " : Can only upgrade profiles user types"); } } /** * Contains details required for an upgrade operation for {@link UserTypeDetails}; */ public static class UserTypeUpgrade { private final String mFromType; private final String mToType; private final int mUpToVersion; public UserTypeUpgrade(String fromType, String toType, int upToVersion) { mFromType = fromType; mToType = toType; mUpToVersion = upToVersion; } public String getFromType() { return mFromType; } public String getToType() { return mToType; } public int getUpToVersion() { return mUpToVersion; } } } services/tests/servicestests/res/xml/usertypes_test_full.xml +3 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,7 @@ <item res='@*android:color/profile_badge_1' /> </badge-colors> </full-type> <change-user-type from="android.old.name" to="android.test.1" whenVersionLeq="1" /> </user-types> Loading
core/res/res/xml/config_user_types.xml +10 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ The following example modifies two AOSP user types (the FULL user android.os.use and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROFILE user type (com.example.profilename): <user-types> <user-types version="0"> <full-type name="android.os.usertype.full.SECONDARY" > <default-restrictions no_sms="true" /> </full-type> Loading @@ -65,6 +65,11 @@ and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROF <profile-type name="com.example.profilename" max-allowed-per-parent="2" /> <change-user-type from="android.os.usertype.profile.MANAGED" to="com.example.profilename" whenVersionLeq="1" /> </user-types> Mandatory attributes: Loading Loading @@ -93,6 +98,10 @@ If this file is updated, the properties of any pre-existing user types will be u Note, however, that default-restrictions refers to the restrictions applied at the time of user creation; therefore, the active restrictions of any pre-existing users will not be updated. The 'change-user-type' tag should be used in conjunction with the 'version' property of 'user-types'. It defines a type change for all pre-existing users of 'from' type to the new 'to' type, if the former 'user-type's version of device is less than or equal to 'whenVersionLeq'. --> <user-types> </user-types>
services/core/java/com/android/server/pm/UserManagerService.java +120 −4 Original line number Diff line number Diff line Loading @@ -119,7 +119,6 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileDescriptor; Loading Loading @@ -175,6 +174,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_CONVERTED_FROM_PRE_CREATED = "convertedFromPreCreated"; private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove"; private static final String ATTR_USER_VERSION = "version"; private static final String ATTR_USER_TYPE_VERSION = "userTypeConfigVersion"; private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId"; private static final String ATTR_PROFILE_BADGE = "profileBadge"; private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId"; Loading Loading @@ -422,6 +422,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mPackagesLock") private int mNextSerialNumber; private int mUserVersion = 0; private int mUserTypeVersion = 0; private IAppOpsService mAppOpsService; Loading Loading @@ -2565,6 +2566,8 @@ public class UserManagerService extends IUserManager.Stub { parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber); mUserVersion = parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion); mUserTypeVersion = parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); } // Pre-O global user restriction were stored as a single bundle (as opposed to per-user Loading Loading @@ -2627,7 +2630,7 @@ public class UserManagerService extends IUserManager.Stub { */ @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) private void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions) { upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion); upgradeIfNecessaryLP(oldGlobalUserRestrictions, mUserVersion, mUserTypeVersion); } /** Loading @@ -2636,9 +2639,11 @@ public class UserManagerService extends IUserManager.Stub { */ @GuardedBy({"mRestrictionsLock", "mPackagesLock"}) @VisibleForTesting void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion) { void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion, int userTypeVersion) { Set<Integer> userIdsToWrite = new ArraySet<>(); final int originalVersion = mUserVersion; final int originalUserTypeVersion = mUserTypeVersion; if (userVersion < 1) { // Assign a proper name for the owner, if not initialized correctly before UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM); Loading Loading @@ -2771,13 +2776,24 @@ public class UserManagerService extends IUserManager.Stub { userVersion = 9; } // Done with userVersion changes, moving on to deal with userTypeVersion upgrades // Upgrade from previous user type to a new user type final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion(); if (newUserTypeVersion > userTypeVersion) { synchronized (mUsersLock) { upgradeUserTypesLU(UserTypeFactory.getUserTypeUpgrades(), mUserTypes, userTypeVersion, userIdsToWrite); } } if (userVersion < USER_VERSION) { Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to " + USER_VERSION); } else { mUserVersion = userVersion; mUserTypeVersion = newUserTypeVersion; if (originalVersion < mUserVersion) { if (originalVersion < mUserVersion || originalUserTypeVersion < mUserTypeVersion) { for (int userId : userIdsToWrite) { UserData userData = getUserDataNoChecks(userId); if (userData != null) { Loading @@ -2801,6 +2817,7 @@ public class UserManagerService extends IUserManager.Stub { UserData userData = putUserInfo(system); mNextSerialNumber = MIN_USER_ID; mUserVersion = USER_VERSION; mUserTypeVersion = UserTypeFactory.getUserTypeVersion(); Bundle restrictions = new Bundle(); try { Loading Loading @@ -2991,6 +3008,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.startTag(null, TAG_USERS); serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber); serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion); serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion); serializer.startTag(null, TAG_GUEST_RESTRICTIONS); synchronized (mGuestRestrictions) { Loading Loading @@ -4957,6 +4975,7 @@ public class UserManagerService extends IUserManager.Stub { // Dump UserTypes pw.println(); pw.println("User types version: " + mUserTypeVersion); pw.println("User types (" + mUserTypes.size() + " types):"); for (int i = 0; i < mUserTypes.size(); i++) { pw.println(" " + mUserTypes.keyAt(i) + ": "); Loading Loading @@ -5447,6 +5466,9 @@ public class UserManagerService extends IUserManager.Stub { * 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. * Under certain circumstances (such as after a change-user-type) the max value can actually * be exceeded: this is allowed in order to keep the device in a usable state. * An error is logged in {@link UserManagerService#upgradeProfileToTypeLU} */ private static int getMaxUsersOfTypePerParent(UserTypeDetails userTypeDetails) { final int defaultMax = userTypeDetails.getMaxAllowedPerParent(); Loading Loading @@ -5534,4 +5556,98 @@ public class UserManagerService extends IUserManager.Stub { } return mDevicePolicyManagerInternal; } @GuardedBy("mUsersLock") @VisibleForTesting void upgradeUserTypesLU(@NonNull List<UserTypeFactory.UserTypeUpgrade> upgradeOps, @NonNull ArrayMap<String, UserTypeDetails> userTypes, final int formerUserTypeVersion, @NonNull Set<Integer> userIdsToWrite) { for (UserTypeFactory.UserTypeUpgrade userTypeUpgrade : upgradeOps) { if (DBG) { Slog.i(LOG_TAG, "Upgrade: " + userTypeUpgrade.getFromType() + " to: " + userTypeUpgrade.getToType() + " maxVersion: " + userTypeUpgrade.getUpToVersion()); } // upgrade user type if version up to getUpToVersion() if (formerUserTypeVersion <= userTypeUpgrade.getUpToVersion()) { for (int i = 0; i < mUsers.size(); i++) { UserData userData = mUsers.valueAt(i); if (userTypeUpgrade.getFromType().equals(userData.info.userType)) { final UserTypeDetails newUserType = userTypes.get( userTypeUpgrade.getToType()); if (newUserType == null) { throw new IllegalStateException( "Upgrade destination user type not defined: " + userTypeUpgrade.getToType()); } upgradeProfileToTypeLU(userData.info, newUserType); userIdsToWrite.add(userData.info.id); } } } } } /** * Changes the user type of a profile to a new user type. * @param userInfo The user to be updated. * @param newUserType The new user type. */ @GuardedBy("mUsersLock") @VisibleForTesting void upgradeProfileToTypeLU(@NonNull UserInfo userInfo, @NonNull UserTypeDetails newUserType) { Slog.i(LOG_TAG, "Upgrading user " + userInfo.id + " from " + userInfo.userType + " to " + newUserType.getName()); if (!userInfo.isProfile()) { throw new IllegalStateException( "Can only upgrade profile types. " + userInfo.userType + " is not a profile type."); } // Exceeded maximum profiles for parent user: log error, but allow upgrade if (!canAddMoreProfilesToUser(newUserType.getName(), userInfo.profileGroupId, false)) { Slog.w(LOG_TAG, "Exceeded maximum profiles of type " + newUserType.getName() + " for user " + userInfo.id + ". Maximum allowed= " + newUserType.getMaxAllowedPerParent()); } final UserTypeDetails oldUserType = mUserTypes.get(userInfo.userType); final int oldFlags; if (oldUserType != null) { oldFlags = oldUserType.getDefaultUserInfoFlags(); } else { // if oldUserType is missing from config_user_types.xml -> can only assume FLAG_PROFILE oldFlags = UserInfo.FLAG_PROFILE; } //convert userData to newUserType userInfo.userType = newUserType.getName(); // remove old default flags and add newUserType's default flags userInfo.flags = newUserType.getDefaultUserInfoFlags() | (userInfo.flags ^ oldFlags); // merge existing base restrictions with the new type's default restrictions synchronized (mRestrictionsLock) { if (!UserRestrictionsUtils.isEmpty(newUserType.getDefaultRestrictions())) { final Bundle newRestrictions = UserRestrictionsUtils.clone( mBaseUserRestrictions.getRestrictions(userInfo.id)); UserRestrictionsUtils.merge(newRestrictions, newUserType.getDefaultRestrictions()); updateUserRestrictionsInternalLR(newRestrictions, userInfo.id); if (DBG) { Slog.i(LOG_TAG, "Updated user " + userInfo.id + " restrictions to " + newRestrictions); } } } // re-compute badge index userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType); } }
services/core/java/com/android/server/pm/UserTypeDetails.java +3 −0 Original line number Diff line number Diff line Loading @@ -174,6 +174,9 @@ public final class UserTypeDetails { /** * Returns the maximum number of this user type allowed per parent (for user types, like * profiles, that have parents). * Under certain circumstances (such as after a change-user-type) the max value can actually * be exceeded: this is allowed in order to keep the device in a usable state. * An error is logged in {@link UserManagerService#upgradeProfileToTypeLU} * <p>Returns {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit. */ public int getMaxAllowedPerParent() { Loading
services/core/java/com/android/server/pm/UserTypeFactory.java +148 −8 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; /** Loading @@ -73,14 +74,7 @@ public final class UserTypeFactory { * @return mapping from the name of each user type to its {@link UserTypeDetails} object */ public static ArrayMap<String, UserTypeDetails> getUserTypes() { final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged()); builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem()); builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary()); builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest()); builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo()); builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted()); builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); final ArrayMap<String, UserTypeDetails.Builder> builders = getDefaultBuilders(); try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { Loading @@ -94,6 +88,20 @@ public final class UserTypeFactory { return types; } private static ArrayMap<String, UserTypeDetails.Builder> getDefaultBuilders() { final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged()); builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem()); builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary()); builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest()); builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo()); builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted()); builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); return builders; } /** * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED} * configuration. Loading Loading @@ -232,6 +240,10 @@ public final class UserTypeFactory { isProfile = true; } else if ("full-type".equals(elementName)) { isProfile = false; } else if ("change-user-type".equals(elementName)) { // parsed in parseUserUpgrades XmlUtils.skipCurrentTag(parser); continue; } else { Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in " + parser.getPositionDescription()); Loading Loading @@ -387,4 +399,132 @@ public final class UserTypeFactory { } fcn.accept(result); } /** * Returns the user type version of the config XML file. * @return user type version defined in XML file, 0 if none. */ public static int getUserTypeVersion() { try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { return getUserTypeVersion(parser); } } @VisibleForTesting static int getUserTypeVersion(XmlResourceParser parser) { int version = 0; try { XmlUtils.beginDocument(parser, "user-types"); String versionValue = parser.getAttributeValue(null, "version"); if (versionValue != null) { try { version = Integer.parseInt(versionValue); } catch (NumberFormatException e) { Slog.e(LOG_TAG, "Cannot parse value of '" + versionValue + "' for version in " + parser.getPositionDescription(), e); throw e; } } } catch (XmlPullParserException | IOException e) { Slog.w(LOG_TAG, "Cannot read user type configuration file.", e); } return version; } /** * Obtains the user type upgrades for this device. * @return The list of user type upgrades. */ public static List<UserTypeUpgrade> getUserTypeUpgrades() { final List<UserTypeUpgrade> userUpgrades; try (XmlResourceParser parser = Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { userUpgrades = parseUserUpgrades(getDefaultBuilders(), parser); } return userUpgrades; } @VisibleForTesting static List<UserTypeUpgrade> parseUserUpgrades( ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser) { final List<UserTypeUpgrade> userUpgrades = new ArrayList<>(); try { XmlUtils.beginDocument(parser, "user-types"); for (XmlUtils.nextElement(parser); parser.getEventType() != XmlResourceParser.END_DOCUMENT; XmlUtils.nextElement(parser)) { final String elementName = parser.getName(); if ("change-user-type".equals(elementName)) { final String fromType = parser.getAttributeValue(null, "from"); final String toType = parser.getAttributeValue(null, "to"); // Check that the base type doesn't change. // Currently, only the base type of PROFILE is supported. validateUserTypeIsProfile(fromType, builders); validateUserTypeIsProfile(toType, builders); final int maxVersionToConvert; try { maxVersionToConvert = Integer.parseInt( parser.getAttributeValue(null, "whenVersionLeq")); } catch (NumberFormatException e) { Slog.e(LOG_TAG, "Cannot parse value of whenVersionLeq in " + parser.getPositionDescription(), e); throw e; } UserTypeUpgrade userTypeUpgrade = new UserTypeUpgrade(fromType, toType, maxVersionToConvert); userUpgrades.add(userTypeUpgrade); continue; } else { XmlUtils.skipCurrentTag(parser); continue; } } } catch (XmlPullParserException | IOException e) { Slog.w(LOG_TAG, "Cannot read user type configuration file.", e); } return userUpgrades; } private static void validateUserTypeIsProfile(String userType, ArrayMap<String, UserTypeDetails.Builder> builders) { UserTypeDetails.Builder builder = builders.get(userType); if (builder != null && builder.getBaseType() != FLAG_PROFILE) { throw new IllegalArgumentException("Illegal upgrade of user type " + userType + " : Can only upgrade profiles user types"); } } /** * Contains details required for an upgrade operation for {@link UserTypeDetails}; */ public static class UserTypeUpgrade { private final String mFromType; private final String mToType; private final int mUpToVersion; public UserTypeUpgrade(String fromType, String toType, int upToVersion) { mFromType = fromType; mToType = toType; mUpToVersion = upToVersion; } public String getFromType() { return mFromType; } public String getToType() { return mToType; } public int getUpToVersion() { return mUpToVersion; } } }
services/tests/servicestests/res/xml/usertypes_test_full.xml +3 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,7 @@ <item res='@*android:color/profile_badge_1' /> </badge-colors> </full-type> <change-user-type from="android.old.name" to="android.test.1" whenVersionLeq="1" /> </user-types>