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);
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index d840e5d8b8828..5fa46b9e46350 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -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}
* Returns {@link #UNLIMITED_NUMBER_OF_USERS} to indicate that there is no hard limit.
*/
public int getMaxAllowedPerParent() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index ba8a2ba6a14ee..2e64a700de9e9 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -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;
/**
@@ -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 getUserTypes() {
- final ArrayMap 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 builders = getDefaultBuilders();
try (XmlResourceParser parser =
Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
@@ -94,6 +88,20 @@ public final class UserTypeFactory {
return types;
}
+ private static ArrayMap getDefaultBuilders() {
+ final ArrayMap 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.
@@ -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());
@@ -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 getUserTypeUpgrades() {
+ final List userUpgrades;
+ try (XmlResourceParser parser =
+ Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
+ userUpgrades = parseUserUpgrades(getDefaultBuilders(), parser);
+ }
+ return userUpgrades;
+ }
+
+ @VisibleForTesting
+ static List parseUserUpgrades(
+ ArrayMap builders, XmlResourceParser parser) {
+ final List 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 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;
+ }
+ }
}
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index a281dcaafb3d7..099ccbe5b5f60 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -22,4 +22,7 @@
+
+
+