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