blob: 14db70e5f72e729ab4cf741c4a9027a809be9c64 [file] [log] [blame]
/*
* 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_ADMIN;
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_MAIN;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
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_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_COMMUNAL;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
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.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
import android.os.Bundle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.R;
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.List;
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 {@link UserManagerServiceUserTypeTest}.
* @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 = getDefaultBuilders();
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;
}
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());
builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal());
builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
return builders;
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
* configuration.
*/
// TODO(b/182396009): Add default restrictions, if needed for clone user type.
private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_CLONE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
.setLabels(R.string.profile_label_clone)
.setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
.setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
// Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
.setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
.setStatusBarIcon(Resources.ID_NULL)
.setBadgeLabels(
com.android.internal.R.string.clone_profile_label_badge)
.setBadgeColors(
com.android.internal.R.color.system_neutral2_800)
.setDarkThemeBadgeColors(
com.android.internal.R.color.system_neutral2_900)
.setDefaultRestrictions(null)
.setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
.setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
.setUseParentsContacts(true)
.setUpdateCrossProfileIntentFiltersOnOTA(true)
.setCrossProfileIntentFilterAccessControl(
UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
.setCrossProfileIntentResolutionStrategy(UserProperties
.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING)
.setShowInQuietMode(
UserProperties.SHOW_IN_QUIET_MODE_DEFAULT)
.setShowInSharingSurfaces(
UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT)
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(true)
.setDeleteAppWithParent(true)
.setCrossProfileContentSharingStrategy(UserProperties
.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
}
/**
* 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)
.setLabels(
R.string.profile_label_work,
R.string.profile_label_work_2,
R.string.profile_label_work_3)
.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)
.setStatusBarIcon(com.android.internal.R.drawable.stat_sys_managed_profile_status)
.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)
.setDarkThemeBadgeColors(
com.android.internal.R.color.profile_badge_1_dark,
com.android.internal.R.color.profile_badge_2_dark,
com.android.internal.R.color.profile_badge_3_dark)
.setDefaultRestrictions(getDefaultProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
.setShowInQuietMode(
UserProperties.SHOW_IN_QUIET_MODE_PAUSED)
.setShowInSharingSurfaces(
UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
.setAuthAlwaysRequiredToDisableQuietMode(false)
.setCredentialShareableWithParent(true));
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_TEST}
* configuration (for userdebug builds). For now it just uses managed profile badges.
*/
private static UserTypeDetails.Builder getDefaultTypeProfileTest() {
final Bundle restrictions = getDefaultProfileRestrictions();
restrictions.putBoolean(UserManager.DISALLOW_FUN, true);
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_TEST)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(2)
.setLabels(
R.string.profile_label_test,
R.string.profile_label_test,
R.string.profile_label_test)
.setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
.setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
.setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
.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)
.setDarkThemeBadgeColors(
com.android.internal.R.color.profile_badge_1_dark,
com.android.internal.R.color.profile_badge_2_dark,
com.android.internal.R.color.profile_badge_3_dark)
.setDefaultRestrictions(restrictions)
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings());
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_COMMUNAL}
* configuration. For now it just uses managed profile badges.
*/
private static UserTypeDetails.Builder getDefaultTypeProfileCommunal() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_COMMUNAL)
.setBaseType(FLAG_PROFILE)
.setMaxAllowed(1)
.setEnabled(UserManager.isCommunalProfileEnabled() ? 1 : 0)
.setLabels(R.string.profile_label_communal)
.setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
.setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
.setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
.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)
.setDarkThemeBadgeColors(
com.android.internal.R.color.profile_badge_1_dark,
com.android.internal.R.color.profile_badge_2_dark,
com.android.internal.R.color.profile_badge_3_dark)
.setDefaultRestrictions(getDefaultProfileRestrictions())
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
.setCredentialShareableWithParent(false)
.setAlwaysVisible(true));
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_PRIVATE}
* configuration.
*/
private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_PRIVATE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
.setLabels(R.string.profile_label_private)
.setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
.setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
// Private Profile doesn't use BadgeNoBackground, so just set to BadgePlain
// as a placeholder.
.setBadgeNoBackground(com.android.internal.R.drawable.ic_private_profile_badge)
.setStatusBarIcon(com.android.internal.R.drawable.stat_sys_private_profile_status)
.setBadgeLabels(
com.android.internal.R.string.private_profile_label_badge)
.setBadgeColors(
R.color.black)
.setDarkThemeBadgeColors(
R.color.white)
.setDefaultRestrictions(getDefaultProfileRestrictions())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setCredentialShareableWithParent(true)
.setAuthAlwaysRequiredToDisableQuietMode(true)
.setAllowStoppingUserWithDelayedLocking(true)
.setMediaSharedWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
.setShowInQuietMode(
UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
.setShowInSharingSurfaces(
UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
.setCrossProfileIntentFilterAccessControl(
UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
.setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
.setCrossProfileContentSharingStrategy(
UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
}
/**
* 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)
.setDefaultUserInfoPropertyFlags(FLAG_PRIMARY | FLAG_ADMIN | FLAG_MAIN)
.setMaxAllowed(1);
}
/**
* 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)
.setDefaultUserInfoPropertyFlags(FLAG_PRIMARY | FLAG_ADMIN)
.setMaxAllowed(1);
}
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);
restrictions.putBoolean(UserManager.DISALLOW_CONFIG_CREDENTIALS, true);
return restrictions;
}
private static Bundle getDefaultProfileRestrictions() {
final Bundle restrictions = new Bundle();
restrictions.putBoolean(UserManager.DISALLOW_WALLPAPER, true);
return restrictions;
}
private static Bundle getDefaultManagedProfileSecureSettings() {
// Only add String values to the bundle, settings are written as Strings eventually
final Bundle settings = new Bundle();
settings.putString(
android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, "1");
settings.putString(
android.provider.Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, "1");
return settings;
}
private static List<DefaultCrossProfileIntentFilter>
getDefaultManagedCrossProfileIntentFilter() {
return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
}
private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
}
/** Gets a default bundle, keyed by Settings.Secure String names, for non-managed profiles. */
private static Bundle getDefaultNonManagedProfileSecureSettings() {
final Bundle settings = new Bundle();
// Non-managed profiles go through neither SetupWizard nor DPC flows, so we automatically
// mark them as setup.
settings.putString(android.provider.Settings.Secure.USER_SETUP_COMPLETE, "1");
return settings;
}
/**
* 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.
*/
// TODO(b/176973369): Add parsing logic to support custom settings/filters
// in config_user_types.xml
@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 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());
XmlUtils.skipCurrentTag(parser);
continue;
}
String typeName = parser.getAttributeValue(null, "name");
if (typeName == null || typeName.equals("")) {
Slog.w(LOG_TAG, "Skipping user type with no name in "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
typeName = 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);
setResAttribute(parser, "status-bar-icon", builder::setStatusBarIcon);
}
setIntAttribute(parser, "enabled", builder::setEnabled);
setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
// 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(XmlUtils.makeTyped(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 if (isProfile && "badge-colors-dark".equals(childName)) {
setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
} else if ("user-properties".equals(childName)) {
builder.getDefaultUserProperties()
.updateFromXml(XmlUtils.makeTyped(parser));
} 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);
}
/**
* 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;
}
}
}