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