1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm;
18 
19 import static android.content.pm.UserInfo.FLAG_ADMIN;
20 import static android.content.pm.UserInfo.FLAG_DEMO;
21 import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
22 import static android.content.pm.UserInfo.FLAG_FULL;
23 import static android.content.pm.UserInfo.FLAG_GUEST;
24 import static android.content.pm.UserInfo.FLAG_MAIN;
25 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
26 import static android.content.pm.UserInfo.FLAG_PRIMARY;
27 import static android.content.pm.UserInfo.FLAG_PROFILE;
28 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
29 import static android.content.pm.UserInfo.FLAG_SYSTEM;
30 import static android.os.UserManager.USER_TYPE_FULL_DEMO;
31 import static android.os.UserManager.USER_TYPE_FULL_GUEST;
32 import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
33 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
34 import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
35 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
36 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
37 import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
38 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
39 
40 import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
41 
42 import android.content.pm.UserInfo;
43 import android.content.pm.UserProperties;
44 import android.content.res.Resources;
45 import android.content.res.XmlResourceParser;
46 import android.os.Build;
47 import android.os.Bundle;
48 import android.os.UserManager;
49 import android.util.ArrayMap;
50 import android.util.Slog;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.util.XmlUtils;
54 
55 import org.xmlpull.v1.XmlPullParserException;
56 
57 import java.io.IOException;
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.function.Consumer;
61 
62 /**
63  * Class for creating all {@link UserTypeDetails} on the device.
64  *
65  * This class is responsible both for defining the AOSP use types, as well as reading in customized
66  * user types from {@link com.android.internal.R.xml#config_user_types}.
67  *
68  * Tests are located in {@link UserManagerServiceUserTypeTest}.
69  * @hide
70  */
71 public final class UserTypeFactory {
72 
73     private static final String LOG_TAG = "UserTypeFactory";
74 
75     /** This is a utility class, so no instantiable constructor. */
UserTypeFactory()76     private UserTypeFactory() {}
77 
78     /**
79      * Obtains the user types (built-in and customized) for this device.
80      *
81      * @return mapping from the name of each user type to its {@link UserTypeDetails} object
82      */
getUserTypes()83     public static ArrayMap<String, UserTypeDetails> getUserTypes() {
84         final ArrayMap<String, UserTypeDetails.Builder> builders = getDefaultBuilders();
85 
86         try (XmlResourceParser parser =
87                      Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
88             customizeBuilders(builders, parser);
89         }
90 
91         final ArrayMap<String, UserTypeDetails> types = new ArrayMap<>(builders.size());
92         for (int i = 0; i < builders.size(); i++) {
93             types.put(builders.keyAt(i), builders.valueAt(i).createUserTypeDetails());
94         }
95         return types;
96     }
97 
getDefaultBuilders()98     private static ArrayMap<String, UserTypeDetails.Builder> getDefaultBuilders() {
99         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
100 
101         builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged());
102         builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem());
103         builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary());
104         builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest());
105         builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
106         builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
107         builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
108         builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
109         if (Build.IS_DEBUGGABLE) {
110             builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
111         }
112 
113         return builders;
114     }
115 
116     /**
117      * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
118      * configuration.
119      */
120     // TODO(b/182396009): Add default restrictions, if needed for clone user type.
getDefaultTypeProfileClone()121     private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
122         return new UserTypeDetails.Builder()
123                 .setName(USER_TYPE_PROFILE_CLONE)
124                 .setBaseType(FLAG_PROFILE)
125                 .setMaxAllowedPerParent(1)
126                 .setLabel(0)
127                 .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
128                 .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
129                 // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
130                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
131                 .setBadgeLabels(
132                         com.android.internal.R.string.clone_profile_label_badge)
133                 .setBadgeColors(
134                         com.android.internal.R.color.system_neutral2_800)
135                 .setDarkThemeBadgeColors(
136                         com.android.internal.R.color.system_neutral2_900)
137                 .setDefaultRestrictions(null)
138                 .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
139                 .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
140                 .setDefaultUserProperties(new UserProperties.Builder()
141                         .setStartWithParent(true)
142                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
143                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
144                         .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
145                         .setUseParentsContacts(true)
146                         .setUpdateCrossProfileIntentFiltersOnOTA(true)
147                         .setCrossProfileIntentFilterAccessControl(
148                                 UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
149                         .setCrossProfileIntentResolutionStrategy(UserProperties
150                                 .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING)
151                         .setMediaSharedWithParent(true)
152                         .setCredentialShareableWithParent(true)
153                         .setDeleteAppWithParent(true));
154     }
155 
156     /**
157      * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
158      * configuration.
159      */
getDefaultTypeProfileManaged()160     private static UserTypeDetails.Builder getDefaultTypeProfileManaged() {
161         return new UserTypeDetails.Builder()
162                 .setName(USER_TYPE_PROFILE_MANAGED)
163                 .setBaseType(FLAG_PROFILE)
164                 .setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE)
165                 .setMaxAllowedPerParent(1)
166                 .setLabel(0)
167                 .setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case)
168                 .setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case)
169                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background)
170                 .setBadgeLabels(
171                         com.android.internal.R.string.managed_profile_label_badge,
172                         com.android.internal.R.string.managed_profile_label_badge_2,
173                         com.android.internal.R.string.managed_profile_label_badge_3)
174                 .setBadgeColors(
175                         com.android.internal.R.color.profile_badge_1,
176                         com.android.internal.R.color.profile_badge_2,
177                         com.android.internal.R.color.profile_badge_3)
178                 .setDarkThemeBadgeColors(
179                         com.android.internal.R.color.profile_badge_1_dark,
180                         com.android.internal.R.color.profile_badge_2_dark,
181                         com.android.internal.R.color.profile_badge_3_dark)
182                 .setDefaultRestrictions(getDefaultManagedProfileRestrictions())
183                 .setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
184                 .setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
185                 .setDefaultUserProperties(new UserProperties.Builder()
186                         .setStartWithParent(true)
187                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
188                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
189                         .setCredentialShareableWithParent(true));
190     }
191 
192     /**
193      * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_TEST}
194      * configuration (for userdebug builds). For now it just uses managed profile badges.
195      */
getDefaultTypeProfileTest()196     private static UserTypeDetails.Builder getDefaultTypeProfileTest() {
197         final Bundle restrictions = new Bundle();
198         restrictions.putBoolean(UserManager.DISALLOW_FUN, true);
199 
200         return new UserTypeDetails.Builder()
201                 .setName(USER_TYPE_PROFILE_TEST)
202                 .setBaseType(FLAG_PROFILE)
203                 .setMaxAllowedPerParent(2)
204                 .setLabel(0)
205                 .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
206                 .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
207                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
208                 .setBadgeLabels(
209                         com.android.internal.R.string.managed_profile_label_badge,
210                         com.android.internal.R.string.managed_profile_label_badge_2,
211                         com.android.internal.R.string.managed_profile_label_badge_3)
212                 .setBadgeColors(
213                         com.android.internal.R.color.profile_badge_1,
214                         com.android.internal.R.color.profile_badge_2,
215                         com.android.internal.R.color.profile_badge_3)
216                 .setDarkThemeBadgeColors(
217                         com.android.internal.R.color.profile_badge_1_dark,
218                         com.android.internal.R.color.profile_badge_2_dark,
219                         com.android.internal.R.color.profile_badge_3_dark)
220                 .setDefaultRestrictions(restrictions)
221                 .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings());
222     }
223 
224     /**
225      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY}
226      * configuration.
227      */
getDefaultTypeFullSecondary()228     private static UserTypeDetails.Builder getDefaultTypeFullSecondary() {
229         return new UserTypeDetails.Builder()
230                 .setName(USER_TYPE_FULL_SECONDARY)
231                 .setBaseType(FLAG_FULL)
232                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
233                 .setDefaultRestrictions(getDefaultSecondaryUserRestrictions());
234     }
235 
236     /**
237      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_GUEST} configuration.
238      */
getDefaultTypeFullGuest()239     private static UserTypeDetails.Builder getDefaultTypeFullGuest() {
240         final boolean ephemeralGuests = Resources.getSystem()
241                 .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
242         final int flags = FLAG_GUEST | (ephemeralGuests ? FLAG_EPHEMERAL : 0);
243 
244         return new UserTypeDetails.Builder()
245                 .setName(USER_TYPE_FULL_GUEST)
246                 .setBaseType(FLAG_FULL)
247                 .setDefaultUserInfoPropertyFlags(flags)
248                 .setMaxAllowed(1)
249                 .setDefaultRestrictions(getDefaultGuestUserRestrictions());
250     }
251 
252     /**
253      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_DEMO} configuration.
254      */
getDefaultTypeFullDemo()255     private static UserTypeDetails.Builder getDefaultTypeFullDemo() {
256         return new UserTypeDetails.Builder()
257                 .setName(USER_TYPE_FULL_DEMO)
258                 .setBaseType(FLAG_FULL)
259                 .setDefaultUserInfoPropertyFlags(FLAG_DEMO)
260                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
261                 .setDefaultRestrictions(null);
262     }
263 
264     /**
265      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_RESTRICTED}
266      * configuration.
267      */
getDefaultTypeFullRestricted()268     private static UserTypeDetails.Builder getDefaultTypeFullRestricted() {
269         return new UserTypeDetails.Builder()
270                 .setName(USER_TYPE_FULL_RESTRICTED)
271                 .setBaseType(FLAG_FULL)
272                 .setDefaultUserInfoPropertyFlags(FLAG_RESTRICTED)
273                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
274                 // NB: UserManagerService.createRestrictedProfile() applies hardcoded restrictions.
275                 .setDefaultRestrictions(null);
276     }
277 
278     /**
279      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration.
280      */
getDefaultTypeFullSystem()281     private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
282         return new UserTypeDetails.Builder()
283                 .setName(USER_TYPE_FULL_SYSTEM)
284                 .setBaseType(FLAG_SYSTEM | FLAG_FULL)
285                 .setDefaultUserInfoPropertyFlags(FLAG_PRIMARY | FLAG_ADMIN | FLAG_MAIN)
286                 .setMaxAllowed(1);
287     }
288 
289     /**
290      * Returns the Builder for the default {@link UserManager#USER_TYPE_SYSTEM_HEADLESS}
291      * configuration.
292      */
getDefaultTypeSystemHeadless()293     private static UserTypeDetails.Builder getDefaultTypeSystemHeadless() {
294         return new UserTypeDetails.Builder()
295                 .setName(USER_TYPE_SYSTEM_HEADLESS)
296                 .setBaseType(FLAG_SYSTEM)
297                 .setDefaultUserInfoPropertyFlags(FLAG_PRIMARY | FLAG_ADMIN)
298                 .setMaxAllowed(1);
299     }
300 
getDefaultSecondaryUserRestrictions()301     private static Bundle getDefaultSecondaryUserRestrictions() {
302         final Bundle restrictions = new Bundle();
303         restrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
304         restrictions.putBoolean(UserManager.DISALLOW_SMS, true);
305         return restrictions;
306     }
307 
getDefaultGuestUserRestrictions()308     private static Bundle getDefaultGuestUserRestrictions() {
309         // Guest inherits the secondary user's restrictions, plus has some extra ones.
310         final Bundle restrictions = getDefaultSecondaryUserRestrictions();
311         restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
312         restrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
313         restrictions.putBoolean(UserManager.DISALLOW_CONFIG_CREDENTIALS, true);
314         return restrictions;
315     }
316 
getDefaultManagedProfileRestrictions()317     private static Bundle getDefaultManagedProfileRestrictions() {
318         final Bundle restrictions = new Bundle();
319         restrictions.putBoolean(UserManager.DISALLOW_WALLPAPER, true);
320         return restrictions;
321     }
322 
getDefaultManagedProfileSecureSettings()323     private static Bundle getDefaultManagedProfileSecureSettings() {
324         // Only add String values to the bundle, settings are written as Strings eventually
325         final Bundle settings = new Bundle();
326         settings.putString(
327                 android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, "1");
328         settings.putString(
329                 android.provider.Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, "1");
330         return settings;
331     }
332 
333     private static List<DefaultCrossProfileIntentFilter>
getDefaultManagedCrossProfileIntentFilter()334             getDefaultManagedCrossProfileIntentFilter() {
335         return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
336     }
337 
getDefaultCloneCrossProfileIntentFilter()338     private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
339         return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
340     }
341 
342     /** Gets a default bundle, keyed by Settings.Secure String names, for non-managed profiles. */
getDefaultNonManagedProfileSecureSettings()343     private static Bundle getDefaultNonManagedProfileSecureSettings() {
344         final Bundle settings = new Bundle();
345         // Non-managed profiles go through neither SetupWizard nor DPC flows, so we automatically
346         // mark them as setup.
347         settings.putString(android.provider.Settings.Secure.USER_SETUP_COMPLETE, "1");
348         return settings;
349     }
350 
351     /**
352      * Reads the given xml parser to obtain device user-type customization, and updates the given
353      * map of {@link UserTypeDetails.Builder}s accordingly.
354      * <p>
355      * The xml file can specify the attributes according to the set... methods below.
356      */
357     // TODO(b/176973369): Add parsing logic to support custom settings/filters
358     //  in config_user_types.xml
359     @VisibleForTesting
customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser)360     static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders,
361             XmlResourceParser parser) {
362         try {
363             XmlUtils.beginDocument(parser, "user-types");
364             for (XmlUtils.nextElement(parser);
365                     parser.getEventType() != XmlResourceParser.END_DOCUMENT;
366                     XmlUtils.nextElement(parser)) {
367                 final boolean isProfile;
368                 final String elementName = parser.getName();
369                 if ("profile-type".equals(elementName)) {
370                     isProfile = true;
371                 } else if ("full-type".equals(elementName)) {
372                     isProfile = false;
373                 } else if ("change-user-type".equals(elementName)) {
374                     // parsed in parseUserUpgrades
375                     XmlUtils.skipCurrentTag(parser);
376                     continue;
377                 } else {
378                     Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in "
379                                 + parser.getPositionDescription());
380                     XmlUtils.skipCurrentTag(parser);
381                     continue;
382                 }
383 
384                 String typeName = parser.getAttributeValue(null, "name");
385                 if (typeName == null || typeName.equals("")) {
386                     Slog.w(LOG_TAG, "Skipping user type with no name in "
387                             + parser.getPositionDescription());
388                     XmlUtils.skipCurrentTag(parser);
389                     continue;
390                 }
391                 typeName = typeName.intern();
392 
393                 UserTypeDetails.Builder builder;
394                 if (typeName.startsWith("android.")) {
395                     // typeName refers to a AOSP-defined type which we are modifying.
396                     Slog.i(LOG_TAG, "Customizing user type " + typeName);
397                     builder = builders.get(typeName);
398                     if (builder == null) {
399                         throw new IllegalArgumentException("Illegal custom user type name "
400                                 + typeName + ": Non-AOSP user types cannot start with 'android.'");
401                     }
402                     final boolean isValid =
403                             (isProfile && builder.getBaseType() == UserInfo.FLAG_PROFILE)
404                             || (!isProfile && builder.getBaseType() == UserInfo.FLAG_FULL);
405                     if (!isValid) {
406                         throw new IllegalArgumentException("Wrong base type to customize user type "
407                                 + "(" + typeName + "), which is type "
408                                 + UserInfo.flagsToString(builder.getBaseType()));
409                     }
410                 } else if (isProfile) {
411                     // typeName refers to a new OEM-defined profile type which we are defining.
412                     Slog.i(LOG_TAG, "Creating custom user type " + typeName);
413                     builder = new UserTypeDetails.Builder();
414                     builder.setName(typeName);
415                     builder.setBaseType(FLAG_PROFILE);
416                     builders.put(typeName, builder);
417                 } else {
418                     throw new IllegalArgumentException("Creation of non-profile user type "
419                             + "(" + typeName + ") is not currently supported.");
420                 }
421 
422                 // Process the attributes (other than name).
423                 if (isProfile) {
424                     setIntAttribute(parser, "max-allowed-per-parent",
425                             builder::setMaxAllowedPerParent);
426                     setResAttribute(parser, "icon-badge", builder::setIconBadge);
427                     setResAttribute(parser, "badge-plain", builder::setBadgePlain);
428                     setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
429                 }
430 
431                 setIntAttribute(parser, "enabled", builder::setEnabled);
432                 setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
433 
434                 // Process child elements.
435                 final int depth = parser.getDepth();
436                 while (XmlUtils.nextElementWithin(parser, depth)) {
437                     final String childName = parser.getName();
438                     if ("default-restrictions".equals(childName)) {
439                         final Bundle restrictions = UserRestrictionsUtils
440                                 .readRestrictions(XmlUtils.makeTyped(parser));
441                         builder.setDefaultRestrictions(restrictions);
442                     } else if (isProfile && "badge-labels".equals(childName)) {
443                         setResAttributeArray(parser, builder::setBadgeLabels);
444                     } else if (isProfile && "badge-colors".equals(childName)) {
445                         setResAttributeArray(parser, builder::setBadgeColors);
446                     } else if (isProfile && "badge-colors-dark".equals(childName)) {
447                         setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
448                     } else if ("user-properties".equals(childName)) {
449                         builder.getDefaultUserProperties()
450                                 .updateFromXml(XmlUtils.makeTyped(parser));
451                     } else {
452                         Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
453                                 + parser.getPositionDescription());
454                     }
455                 }
456             }
457         } catch (XmlPullParserException | IOException e) {
458             Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
459         }
460     }
461 
462     /**
463      * If the given attribute exists, gets the int stored in it and performs the given fcn using it.
464      * The stored value must be an int or NumberFormatException will be thrown.
465      *
466      * @param parser xml parser from which to read the attribute
467      * @param attributeName name of the attribute
468      * @param fcn one-int-argument function,
469      *            like {@link UserTypeDetails.Builder#setMaxAllowedPerParent(int)}
470      */
setIntAttribute(XmlResourceParser parser, String attributeName, Consumer<Integer> fcn)471     private static void setIntAttribute(XmlResourceParser parser, String attributeName,
472             Consumer<Integer> fcn) {
473         final String intValue = parser.getAttributeValue(null, attributeName);
474         if (intValue == null) {
475             return;
476         }
477         try {
478             fcn.accept(Integer.parseInt(intValue));
479         } catch (NumberFormatException e) {
480             Slog.e(LOG_TAG, "Cannot parse value of '" + intValue + "' for " + attributeName
481                     + " in " + parser.getPositionDescription(), e);
482             throw e;
483         }
484     }
485 
486     /**
487      * If the given attribute exists, gets the resId stored in it (or 0 if it is not a valid resId)
488      * and performs the given fcn using it.
489      *
490      * @param parser xml parser from which to read the attribute
491      * @param attributeName name of the attribute
492      * @param fcn one-argument function, like {@link UserTypeDetails.Builder#setIconBadge(int)}
493      */
setResAttribute(XmlResourceParser parser, String attributeName, Consumer<Integer> fcn)494     private static void setResAttribute(XmlResourceParser parser, String attributeName,
495             Consumer<Integer> fcn) {
496         if (parser.getAttributeValue(null, attributeName) == null) {
497             // Attribute is not present, i.e. use the default value.
498             return;
499         }
500         final int resId = parser.getAttributeResourceValue(null, attributeName, Resources.ID_NULL);
501         fcn.accept(resId);
502     }
503 
504     /**
505      * Gets the resIds stored in "item" elements (in their "res" attribute) at the current depth.
506      * Then performs the given fcn using the int[] array of these resIds.
507      * <p>
508      * Each xml element is expected to be of the form {@code <item res="someResValue" />}.
509      *
510      * @param parser xml parser from which to read the elements and their attributes
511      * @param fcn function, like {@link UserTypeDetails.Builder#setBadgeColors(int...)}
512      */
setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)513     private static void setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)
514             throws IOException, XmlPullParserException {
515 
516         ArrayList<Integer> resList = new ArrayList<>();
517         final int depth = parser.getDepth();
518         while (XmlUtils.nextElementWithin(parser, depth)) {
519             final String elementName = parser.getName();
520             if (!"item".equals(elementName)) {
521                 Slog.w(LOG_TAG, "Skipping unknown child element " + elementName + " in "
522                         + parser.getPositionDescription());
523                 XmlUtils.skipCurrentTag(parser);
524                 continue;
525             }
526             final int resId = parser.getAttributeResourceValue(null, "res", -1);
527             if (resId == -1) {
528                 continue;
529             }
530             resList.add(resId);
531         }
532 
533         int[] result = new int[resList.size()];
534         for (int i = 0; i < resList.size(); i++) {
535             result[i] = resList.get(i);
536         }
537         fcn.accept(result);
538     }
539 
540     /**
541      * Returns the user type version of the config XML file.
542      * @return user type version defined in XML file, 0 if none.
543      */
getUserTypeVersion()544     public static int getUserTypeVersion() {
545         try (XmlResourceParser parser =
546                      Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
547             return getUserTypeVersion(parser);
548         }
549     }
550 
551     @VisibleForTesting
getUserTypeVersion(XmlResourceParser parser)552     static int getUserTypeVersion(XmlResourceParser parser) {
553         int version = 0;
554 
555         try {
556             XmlUtils.beginDocument(parser, "user-types");
557             String versionValue = parser.getAttributeValue(null, "version");
558             if (versionValue != null) {
559                 try {
560                     version = Integer.parseInt(versionValue);
561                 } catch (NumberFormatException e) {
562                     Slog.e(LOG_TAG, "Cannot parse value of '" + versionValue + "' for version in "
563                             + parser.getPositionDescription(), e);
564                     throw e;
565                 }
566             }
567         } catch (XmlPullParserException | IOException e) {
568             Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
569         }
570 
571         return version;
572     }
573 
574     /**
575      * Obtains the user type upgrades for this device.
576      * @return The list of user type upgrades.
577      */
getUserTypeUpgrades()578     public static List<UserTypeUpgrade> getUserTypeUpgrades() {
579         final List<UserTypeUpgrade> userUpgrades;
580         try (XmlResourceParser parser =
581                      Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
582             userUpgrades = parseUserUpgrades(getDefaultBuilders(), parser);
583         }
584         return userUpgrades;
585     }
586 
587     @VisibleForTesting
parseUserUpgrades( ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser)588     static List<UserTypeUpgrade> parseUserUpgrades(
589             ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser) {
590         final List<UserTypeUpgrade> userUpgrades = new ArrayList<>();
591 
592         try {
593             XmlUtils.beginDocument(parser, "user-types");
594             for (XmlUtils.nextElement(parser);
595                     parser.getEventType() != XmlResourceParser.END_DOCUMENT;
596                     XmlUtils.nextElement(parser)) {
597                 final String elementName = parser.getName();
598                 if ("change-user-type".equals(elementName)) {
599                     final String fromType = parser.getAttributeValue(null, "from");
600                     final String toType = parser.getAttributeValue(null, "to");
601                     // Check that the base type doesn't change.
602                     // Currently, only the base type of PROFILE is supported.
603                     validateUserTypeIsProfile(fromType, builders);
604                     validateUserTypeIsProfile(toType, builders);
605 
606                     final int maxVersionToConvert;
607                     try {
608                         maxVersionToConvert = Integer.parseInt(
609                                 parser.getAttributeValue(null, "whenVersionLeq"));
610                     } catch (NumberFormatException e) {
611                         Slog.e(LOG_TAG, "Cannot parse value of whenVersionLeq in "
612                                 + parser.getPositionDescription(), e);
613                         throw e;
614                     }
615 
616                     UserTypeUpgrade userTypeUpgrade = new UserTypeUpgrade(fromType, toType,
617                             maxVersionToConvert);
618                     userUpgrades.add(userTypeUpgrade);
619                     continue;
620                 } else {
621                     XmlUtils.skipCurrentTag(parser);
622                     continue;
623                 }
624             }
625         } catch (XmlPullParserException | IOException e) {
626             Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
627         }
628 
629         return userUpgrades;
630     }
631 
validateUserTypeIsProfile(String userType, ArrayMap<String, UserTypeDetails.Builder> builders)632     private static void validateUserTypeIsProfile(String userType,
633             ArrayMap<String, UserTypeDetails.Builder> builders) {
634         UserTypeDetails.Builder builder = builders.get(userType);
635         if (builder != null && builder.getBaseType() != FLAG_PROFILE) {
636             throw new IllegalArgumentException("Illegal upgrade of user type " + userType
637                     + " : Can only upgrade profiles user types");
638         }
639     }
640 
641     /**
642      * Contains details required for an upgrade operation for {@link UserTypeDetails};
643      */
644     public static class UserTypeUpgrade {
645         private final String mFromType;
646         private final String mToType;
647         private final int mUpToVersion;
648 
UserTypeUpgrade(String fromType, String toType, int upToVersion)649         public UserTypeUpgrade(String fromType, String toType, int upToVersion) {
650             mFromType = fromType;
651             mToType = toType;
652             mUpToVersion = upToVersion;
653         }
654 
getFromType()655         public String getFromType() {
656             return mFromType;
657         }
658 
getToType()659         public String getToType() {
660             return mToType;
661         }
662 
getUpToVersion()663         public int getUpToVersion() {
664             return mUpToVersion;
665         }
666     }
667 }
668