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