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