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

Commit 7afa84bb authored by Iavor-Valentin Iftime's avatar Iavor-Valentin Iftime Committed by Android (Google) Code Review
Browse files

Merge "Upgrade profiles to custom user types"

parents 2943c8c9 239207a4
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