| /* |
| * 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.pm.UserInfo; |
| import android.content.res.Resources; |
| import android.content.res.XmlResourceParser; |
| import android.os.Bundle; |
| import android.os.UserManager; |
| import android.util.ArrayMap; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.XmlUtils; |
| |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.function.Consumer; |
| |
| /** |
| * Class for creating all {@link UserTypeDetails} on the device. |
| * |
| * This class is responsible both for defining the AOSP use types, as well as reading in customized |
| * user types from {@link com.android.internal.R.xml#config_user_types}. |
| * |
| * Tests are located in UserManagerServiceUserTypeTest.java. |
| * @hide |
| */ |
| public final class UserTypeFactory { |
| |
| private static final String LOG_TAG = "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.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()); |
| |
| try (XmlResourceParser parser = |
| Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) { |
| customizeBuilders(builders, parser); |
| } |
| |
| final ArrayMap<String, UserTypeDetails> types = new ArrayMap<>(builders.size()); |
| for (int i = 0; i < builders.size(); i++) { |
| types.put(builders.keyAt(i), builders.valueAt(i).createUserTypeDetails()); |
| } |
| return types; |
| } |
| |
| /** |
| * 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) |
| .setDefaultRestrictions(null); |
| } |
| |
| /** |
| * 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) |
| .setDefaultRestrictions(getDefaultSecondaryUserRestrictions()); |
| } |
| |
| /** |
| * 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); |
| |
| return new UserTypeDetails.Builder() |
| .setName(USER_TYPE_FULL_GUEST) |
| .setBaseType(FLAG_FULL) |
| .setDefaultUserInfoPropertyFlags(flags) |
| .setMaxAllowed(1) |
| .setDefaultRestrictions(getDefaultGuestUserRestrictions()); |
| } |
| |
| /** |
| * 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) |
| .setDefaultRestrictions(null); |
| } |
| |
| /** |
| * 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) |
| // NB: UserManagerService.createRestrictedProfile() applies hardcoded restrictions. |
| .setDefaultRestrictions(null); |
| } |
| |
| /** |
| * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration. |
| */ |
| private static UserTypeDetails.Builder getDefaultTypeFullSystem() { |
| 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); |
| } |
| |
| private static Bundle getDefaultSecondaryUserRestrictions() { |
| final Bundle restrictions = new Bundle(); |
| restrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true); |
| restrictions.putBoolean(UserManager.DISALLOW_SMS, true); |
| return restrictions; |
| } |
| |
| private static Bundle getDefaultGuestUserRestrictions() { |
| // Guest inherits the secondary user's restrictions, plus has some extra ones. |
| final Bundle restrictions = getDefaultSecondaryUserRestrictions(); |
| restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true); |
| restrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true); |
| return restrictions; |
| } |
| |
| /** |
| * Reads the given xml parser to obtain device user-type customization, and updates the given |
| * map of {@link UserTypeDetails.Builder}s accordingly. |
| * <p> |
| * The xml file can specify the attributes according to the set... methods below. |
| */ |
| @VisibleForTesting |
| static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders, |
| XmlResourceParser parser) { |
| try { |
| XmlUtils.beginDocument(parser, "user-types"); |
| for (XmlUtils.nextElement(parser); |
| parser.getEventType() != XmlResourceParser.END_DOCUMENT; |
| XmlUtils.nextElement(parser)) { |
| final boolean isProfile; |
| final String elementName = parser.getName(); |
| if ("profile-type".equals(elementName)) { |
| isProfile = true; |
| } else if ("full-type".equals(elementName)) { |
| isProfile = false; |
| } else { |
| Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| |
| String typeName = parser.getAttributeValue(null, "name"); |
| if (typeName == null) { |
| Slog.w(LOG_TAG, "Skipping user type with no name in " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| typeName.intern(); |
| |
| UserTypeDetails.Builder builder; |
| if (typeName.startsWith("android.")) { |
| // typeName refers to a AOSP-defined type which we are modifying. |
| Slog.i(LOG_TAG, "Customizing user type " + typeName); |
| builder = builders.get(typeName); |
| if (builder == null) { |
| throw new IllegalArgumentException("Illegal custom user type name " |
| + typeName + ": Non-AOSP user types cannot start with 'android.'"); |
| } |
| final boolean isValid = |
| (isProfile && builder.getBaseType() == UserInfo.FLAG_PROFILE) |
| || (!isProfile && builder.getBaseType() == UserInfo.FLAG_FULL); |
| if (!isValid) { |
| throw new IllegalArgumentException("Wrong base type to customize user type " |
| + "(" + typeName + "), which is type " |
| + UserInfo.flagsToString(builder.getBaseType())); |
| } |
| } else if (isProfile) { |
| // typeName refers to a new OEM-defined profile type which we are defining. |
| Slog.i(LOG_TAG, "Creating custom user type " + typeName); |
| builder = new UserTypeDetails.Builder(); |
| builder.setName(typeName); |
| builder.setBaseType(FLAG_PROFILE); |
| builders.put(typeName, builder); |
| } else { |
| throw new IllegalArgumentException("Creation of non-profile user type " |
| + "(" + typeName + ") is not currently supported."); |
| } |
| |
| // Process the attributes (other than name). |
| if (isProfile) { |
| setIntAttribute(parser, "max-allowed-per-parent", |
| builder::setMaxAllowedPerParent); |
| setResAttribute(parser, "icon-badge", builder::setIconBadge); |
| setResAttribute(parser, "badge-plain", builder::setBadgePlain); |
| setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground); |
| } |
| |
| // Process child elements. |
| final int depth = parser.getDepth(); |
| while (XmlUtils.nextElementWithin(parser, depth)) { |
| final String childName = parser.getName(); |
| if ("default-restrictions".equals(childName)) { |
| final Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser); |
| builder.setDefaultRestrictions(restrictions); |
| } else if (isProfile && "badge-labels".equals(childName)) { |
| setResAttributeArray(parser, builder::setBadgeLabels); |
| } else if (isProfile && "badge-colors".equals(childName)) { |
| setResAttributeArray(parser, builder::setBadgeColors); |
| } else { |
| Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in " |
| + parser.getPositionDescription()); |
| } |
| } |
| } |
| } catch (XmlPullParserException | IOException e) { |
| Slog.w(LOG_TAG, "Cannot read user type configuration file.", e); |
| } |
| } |
| |
| /** |
| * If the given attribute exists, gets the int stored in it and performs the given fcn using it. |
| * The stored value must be an int or NumberFormatException will be thrown. |
| * |
| * @param parser xml parser from which to read the attribute |
| * @param attributeName name of the attribute |
| * @param fcn one-int-argument function, |
| * like {@link UserTypeDetails.Builder#setMaxAllowedPerParent(int)} |
| */ |
| private static void setIntAttribute(XmlResourceParser parser, String attributeName, |
| Consumer<Integer> fcn) { |
| final String intValue = parser.getAttributeValue(null, attributeName); |
| if (intValue == null) { |
| return; |
| } |
| try { |
| fcn.accept(Integer.parseInt(intValue)); |
| } catch (NumberFormatException e) { |
| Slog.e(LOG_TAG, "Cannot parse value of '" + intValue + "' for " + attributeName |
| + " in " + parser.getPositionDescription(), e); |
| throw e; |
| } |
| } |
| |
| /** |
| * If the given attribute exists, gets the resId stored in it (or 0 if it is not a valid resId) |
| * and performs the given fcn using it. |
| * |
| * @param parser xml parser from which to read the attribute |
| * @param attributeName name of the attribute |
| * @param fcn one-argument function, like {@link UserTypeDetails.Builder#setIconBadge(int)} |
| */ |
| private static void setResAttribute(XmlResourceParser parser, String attributeName, |
| Consumer<Integer> fcn) { |
| if (parser.getAttributeValue(null, attributeName) == null) { |
| // Attribute is not present, i.e. use the default value. |
| return; |
| } |
| final int resId = parser.getAttributeResourceValue(null, attributeName, Resources.ID_NULL); |
| fcn.accept(resId); |
| } |
| |
| /** |
| * Gets the resIds stored in "item" elements (in their "res" attribute) at the current depth. |
| * Then performs the given fcn using the int[] array of these resIds. |
| * <p> |
| * Each xml element is expected to be of the form {@code <item res="someResValue" />}. |
| * |
| * @param parser xml parser from which to read the elements and their attributes |
| * @param fcn function, like {@link UserTypeDetails.Builder#setBadgeColors(int...)} |
| */ |
| private static void setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn) |
| throws IOException, XmlPullParserException { |
| |
| ArrayList<Integer> resList = new ArrayList<>(); |
| final int depth = parser.getDepth(); |
| while (XmlUtils.nextElementWithin(parser, depth)) { |
| final String elementName = parser.getName(); |
| if (!"item".equals(elementName)) { |
| Slog.w(LOG_TAG, "Skipping unknown child element " + elementName + " in " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| final int resId = parser.getAttributeResourceValue(null, "res", -1); |
| if (resId == -1) { |
| continue; |
| } |
| resList.add(resId); |
| } |
| |
| int[] result = new int[resList.size()]; |
| for (int i = 0; i < resList.size(); i++) { |
| result[i] = resList.get(i); |
| } |
| fcn.accept(result); |
| } |
| } |