Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 239207a4 authored by Valentin Iftime's avatar Valentin Iftime
Browse files

Upgrade profiles to custom user types

 Allow existing (manged) profiles to be converted to custom profile
  types.
 Only convert on system upgrade (using upgradeIfNecessaryLP).

Test: atest UserManagerServiceUserInfoTest; atest
UserManagerServiceUserTypeTest

Bug: 168589581
Change-Id: Ib6431bb4d858b6546509f69dfb7c580aa1c21d48
parent 8c68af5d
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -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>
@@ -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:
@@ -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>
+120 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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";
@@ -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;

@@ -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
@@ -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);
    }

    /**
@@ -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);
@@ -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) {
@@ -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 {
@@ -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) {
@@ -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) + ": ");
@@ -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();
@@ -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);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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() {
+148 −8
Original line number Diff line number Diff line
@@ -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<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)) {
@@ -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.
@@ -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<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;
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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