1 /* 2 * Copyright (C) 2020 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.role.controller.model; 18 19 import android.app.AppOpsManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.Resources; 23 import android.content.res.XmlResourceParser; 24 import android.os.Build; 25 import android.permission.flags.Flags; 26 import android.util.ArrayMap; 27 import android.util.Log; 28 import android.util.Pair; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.modules.utils.build.SdkLevel; 35 import com.android.role.controller.behavior.BrowserRoleBehavior; 36 import com.android.role.controller.util.ResourceUtils; 37 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.IOException; 41 import java.lang.reflect.Method; 42 import java.lang.reflect.Modifier; 43 import java.util.ArrayList; 44 import java.util.Collection; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Objects; 48 import java.util.function.Function; 49 import java.util.function.Supplier; 50 51 /** 52 * Parser for {@link Role} definitions. 53 */ 54 @VisibleForTesting 55 public class RoleParser { 56 57 /** 58 * Function to retrieve the roles.xml resource from a context 59 */ 60 public static volatile Function<Context, XmlResourceParser> sGetRolesXml; 61 62 private static final String LOG_TAG = RoleParser.class.getSimpleName(); 63 64 private static final String TAG_ROLES = "roles"; 65 private static final String TAG_PERMISSION_SET = "permission-set"; 66 private static final String TAG_PERMISSION = "permission"; 67 private static final String TAG_ROLE = "role"; 68 private static final String TAG_REQUIRED_COMPONENTS = "required-components"; 69 private static final String TAG_ACTIVITY = "activity"; 70 private static final String TAG_PROVIDER = "provider"; 71 private static final String TAG_RECEIVER = "receiver"; 72 private static final String TAG_SERVICE = "service"; 73 private static final String TAG_INTENT_FILTER = "intent-filter"; 74 private static final String TAG_ACTION = "action"; 75 private static final String TAG_CATEGORY = "category"; 76 private static final String TAG_DATA = "data"; 77 private static final String TAG_META_DATA = "meta-data"; 78 private static final String TAG_PERMISSIONS = "permissions"; 79 private static final String TAG_APP_OP_PERMISSIONS = "app-op-permissions"; 80 private static final String TAG_APP_OP_PERMISSION = "app-op-permission"; 81 private static final String TAG_APP_OPS = "app-ops"; 82 private static final String TAG_APP_OP = "app-op"; 83 private static final String TAG_PREFERRED_ACTIVITIES = "preferred-activities"; 84 private static final String TAG_PREFERRED_ACTIVITY = "preferred-activity"; 85 private static final String ATTRIBUTE_NAME = "name"; 86 private static final String ATTRIBUTE_ALLOW_BYPASSING_QUALIFICATION = 87 "allowBypassingQualification"; 88 private static final String ATTRIBUTE_BEHAVIOR = "behavior"; 89 private static final String ATTRIBUTE_DEFAULT_HOLDERS = "defaultHolders"; 90 private static final String ATTRIBUTE_DESCRIPTION = "description"; 91 private static final String ATTRIBUTE_EXCLUSIVE = "exclusive"; 92 private static final String ATTRIBUTE_EXCLUSIVITY = "exclusivity"; 93 private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder"; 94 private static final String ATTRIBUTE_FEATURE_FLAG = "featureFlag"; 95 private static final String ATTRIBUTE_LABEL = "label"; 96 private static final String ATTRIBUTE_MAX_SDK_VERSION = "maxSdkVersion"; 97 private static final String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; 98 private static final String ATTRIBUTE_ONLY_GRANT_WHEN_ADDED = "onlyGrantWhenAdded"; 99 private static final String ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING = "overrideUserWhenGranting"; 100 private static final String ATTRIBUTE_QUERY_FLAGS = "queryFlags"; 101 private static final String ATTRIBUTE_REQUEST_TITLE = "requestTitle"; 102 private static final String ATTRIBUTE_REQUEST_DESCRIPTION = "requestDescription"; 103 private static final String ATTRIBUTE_REQUESTABLE = "requestable"; 104 private static final String ATTRIBUTE_SEARCH_KEYWORDS = "searchKeywords"; 105 private static final String ATTRIBUTE_SHORT_LABEL = "shortLabel"; 106 private static final String ATTRIBUTE_SHOW_NONE = "showNone"; 107 private static final String ATTRIBUTE_STATIC = "static"; 108 private static final String ATTRIBUTE_SYSTEM_ONLY = "systemOnly"; 109 private static final String ATTRIBUTE_UI_BEHAVIOR = "uiBehavior"; 110 private static final String ATTRIBUTE_VISIBLE = "visible"; 111 private static final String ATTRIBUTE_FLAGS = "flags"; 112 private static final String ATTRIBUTE_MIN_TARGET_SDK_VERSION = "minTargetSdkVersion"; 113 private static final String ATTRIBUTE_OPTIONAL_MIN_SDK_VERSION = "optionalMinSdkVersion"; 114 private static final String ATTRIBUTE_PERMISSION = "permission"; 115 private static final String ATTRIBUTE_PROHIBITED = "prohibited"; 116 private static final String ATTRIBUTE_VALUE = "value"; 117 private static final String ATTRIBUTE_SCHEME = "scheme"; 118 private static final String ATTRIBUTE_MIME_TYPE = "mimeType"; 119 private static final String ATTRIBUTE_MAX_TARGET_SDK_VERSION = "maxTargetSdkVersion"; 120 private static final String ATTRIBUTE_MODE = "mode"; 121 122 private static final String BEHAVIOR_PACKAGE_NAME = BrowserRoleBehavior.class.getPackage() 123 .getName(); 124 125 private static final String MODE_NAME_ALLOWED = "allowed"; 126 private static final String MODE_NAME_IGNORED = "ignored"; 127 private static final String MODE_NAME_ERRORED = "errored"; 128 private static final String MODE_NAME_DEFAULT = "default"; 129 private static final String MODE_NAME_FOREGROUND = "foreground"; 130 private static final ArrayMap<String, Integer> sModeNameToMode = new ArrayMap<>(); 131 static { sModeNameToMode.put(MODE_NAME_ALLOWED, AppOpsManager.MODE_ALLOWED)132 sModeNameToMode.put(MODE_NAME_ALLOWED, AppOpsManager.MODE_ALLOWED); sModeNameToMode.put(MODE_NAME_IGNORED, AppOpsManager.MODE_IGNORED)133 sModeNameToMode.put(MODE_NAME_IGNORED, AppOpsManager.MODE_IGNORED); sModeNameToMode.put(MODE_NAME_ERRORED, AppOpsManager.MODE_ERRORED)134 sModeNameToMode.put(MODE_NAME_ERRORED, AppOpsManager.MODE_ERRORED); sModeNameToMode.put(MODE_NAME_DEFAULT, AppOpsManager.MODE_DEFAULT)135 sModeNameToMode.put(MODE_NAME_DEFAULT, AppOpsManager.MODE_DEFAULT); sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND)136 sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND); 137 } 138 139 private static final String EXCLUSIVITY_NONE = "none"; 140 private static final String EXCLUSIVITY_USER = "user"; 141 private static final String EXCLUSIVITY_PROFILE_GROUP = "profileGroup"; 142 143 private static final Supplier<Boolean> sFeatureFlagFallback = () -> false; 144 145 private static final ArrayMap<Class<?>, Class<?>> sPrimitiveToWrapperClass = new ArrayMap<>(); 146 static { sPrimitiveToWrapperClass.put(Boolean.TYPE, Boolean.class)147 sPrimitiveToWrapperClass.put(Boolean.TYPE, Boolean.class); sPrimitiveToWrapperClass.put(Byte.TYPE, Byte.class)148 sPrimitiveToWrapperClass.put(Byte.TYPE, Byte.class); sPrimitiveToWrapperClass.put(Character.TYPE, Character.class)149 sPrimitiveToWrapperClass.put(Character.TYPE, Character.class); sPrimitiveToWrapperClass.put(Short.TYPE, Short.class)150 sPrimitiveToWrapperClass.put(Short.TYPE, Short.class); sPrimitiveToWrapperClass.put(Integer.TYPE, Integer.class)151 sPrimitiveToWrapperClass.put(Integer.TYPE, Integer.class); sPrimitiveToWrapperClass.put(Long.TYPE, Long.class)152 sPrimitiveToWrapperClass.put(Long.TYPE, Long.class); sPrimitiveToWrapperClass.put(Float.TYPE, Float.class)153 sPrimitiveToWrapperClass.put(Float.TYPE, Float.class); sPrimitiveToWrapperClass.put(Double.TYPE, Double.class)154 sPrimitiveToWrapperClass.put(Double.TYPE, Double.class); sPrimitiveToWrapperClass.put(Void.TYPE, Void.class)155 sPrimitiveToWrapperClass.put(Void.TYPE, Void.class); 156 } 157 158 @NonNull 159 private final Context mContext; 160 161 private final boolean mThrowOnError; 162 RoleParser(@onNull Context context)163 public RoleParser(@NonNull Context context) { 164 this(context, false); 165 } 166 167 @VisibleForTesting RoleParser(@onNull Context context, boolean throwOnError)168 public RoleParser(@NonNull Context context, boolean throwOnError) { 169 mContext = context; 170 mThrowOnError = throwOnError; 171 } 172 173 /** 174 * Parse the roles defined in {@code roles.xml}. 175 * 176 * @return a map from role name to {@link Role} instances 177 */ 178 @NonNull parse()179 public ArrayMap<String, Role> parse() { 180 Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseRolesXml(); 181 if (xml == null) { 182 return new ArrayMap<>(); 183 } 184 return xml.second; 185 } 186 187 @Nullable 188 @VisibleForTesting parseRolesXml()189 public Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRolesXml() { 190 try (XmlResourceParser parser = getRolesXml()) { 191 return parseXml(parser); 192 } catch (XmlPullParserException | IOException e) { 193 throwOrLogMessage("Unable to parse roles.xml", e); 194 return null; 195 } 196 } 197 198 /** 199 * Retrieves the roles.xml resource from a context 200 */ getRolesXml()201 private XmlResourceParser getRolesXml() { 202 if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) { 203 Resources resources = ResourceUtils.getPermissionControllerResources(mContext); 204 int resourceId = resources.getIdentifier("roles", "xml", 205 ResourceUtils.RESOURCE_PACKAGE_NAME_PERMISSION_CONTROLLER); 206 return resources.getXml(resourceId); 207 } else { 208 return sGetRolesXml.apply(mContext); 209 } 210 } 211 212 @Nullable parseXml( @onNull XmlResourceParser parser)213 private Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseXml( 214 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 215 Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = null; 216 217 int type; 218 int depth; 219 int innerDepth = parser.getDepth() + 1; 220 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 221 && ((depth = parser.getDepth()) >= innerDepth 222 || type != XmlResourceParser.END_TAG)) { 223 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 224 continue; 225 } 226 227 if (parser.getName().equals(TAG_ROLES)) { 228 if (xml != null) { 229 throwOrLogMessage("Duplicate <roles>"); 230 skipCurrentTag(parser); 231 continue; 232 } 233 xml = parseRoles(parser); 234 } else { 235 throwOrLogForUnknownTag(parser); 236 skipCurrentTag(parser); 237 } 238 } 239 240 if (xml == null) { 241 throwOrLogMessage("Missing <roles>"); 242 } 243 return xml; 244 } 245 246 @NonNull parseRoles( @onNull XmlResourceParser parser)247 private Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRoles( 248 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 249 ArrayMap<String, PermissionSet> permissionSets = new ArrayMap<>(); 250 ArrayMap<String, Role> roles = new ArrayMap<>(); 251 252 int type; 253 int depth; 254 int innerDepth = parser.getDepth() + 1; 255 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 256 && ((depth = parser.getDepth()) >= innerDepth 257 || type != XmlResourceParser.END_TAG)) { 258 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 259 continue; 260 } 261 262 switch (parser.getName()) { 263 case TAG_PERMISSION_SET: { 264 PermissionSet permissionSet = parsePermissionSet(parser); 265 if (permissionSet == null) { 266 continue; 267 } 268 validateNoDuplicateElement(permissionSet.getName(), permissionSets.keySet(), 269 "permission set"); 270 permissionSets.put(permissionSet.getName(), permissionSet); 271 break; 272 } 273 case TAG_ROLE: { 274 Role role = parseRole(parser, permissionSets); 275 if (role == null) { 276 continue; 277 } 278 validateNoDuplicateElement(role.getName(), roles.keySet(), "role"); 279 roles.put(role.getName(), role); 280 break; 281 } 282 default: 283 throwOrLogForUnknownTag(parser); 284 skipCurrentTag(parser); 285 } 286 } 287 288 return new Pair<>(permissionSets, roles); 289 } 290 291 @Nullable parsePermissionSet(@onNull XmlResourceParser parser)292 private PermissionSet parsePermissionSet(@NonNull XmlResourceParser parser) 293 throws IOException, XmlPullParserException { 294 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_PERMISSION_SET); 295 if (name == null) { 296 skipCurrentTag(parser); 297 return null; 298 } 299 300 Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, ATTRIBUTE_FEATURE_FLAG, 301 boolean.class, sFeatureFlagFallback, TAG_PERMISSION_SET); 302 303 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 304 Build.VERSION_CODES.BASE); 305 int optionalMinSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_OPTIONAL_MIN_SDK_VERSION, 306 minSdkVersion); 307 308 List<Permission> permissions = new ArrayList<>(); 309 310 int type; 311 int depth; 312 int innerDepth = parser.getDepth() + 1; 313 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 314 && ((depth = parser.getDepth()) >= innerDepth 315 || type != XmlResourceParser.END_TAG)) { 316 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 317 continue; 318 } 319 320 if (parser.getName().equals(TAG_PERMISSION)) { 321 Permission permission = parsePermission(parser, TAG_PERMISSION); 322 if (permission == null) { 323 continue; 324 } 325 Supplier<Boolean> mergedFeatureFlag = 326 mergeFeatureFlags(permission.getFeatureFlag(), featureFlag); 327 int mergedMinSdkVersion = Math.max(permission.getMinSdkVersion(), minSdkVersion); 328 int mergedOptionalMinSdkVersion = Math.max(permission.getOptionalMinSdkVersion(), 329 optionalMinSdkVersion); 330 permission = permission.withFeatureFlagAndSdkVersions(mergedFeatureFlag, 331 mergedMinSdkVersion, mergedOptionalMinSdkVersion); 332 validateNoDuplicateElement(permission, permissions, "permission"); 333 permissions.add(permission); 334 } else { 335 throwOrLogForUnknownTag(parser); 336 skipCurrentTag(parser); 337 } 338 } 339 340 return new PermissionSet(name, permissions); 341 } 342 343 @Nullable parsePermission(@onNull XmlResourceParser parser, @NonNull String tagName)344 private Permission parsePermission(@NonNull XmlResourceParser parser, 345 @NonNull String tagName) throws IOException, XmlPullParserException { 346 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, tagName); 347 if (name == null) { 348 skipCurrentTag(parser); 349 return null; 350 } 351 352 Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, ATTRIBUTE_FEATURE_FLAG, 353 boolean.class, sFeatureFlagFallback, TAG_PERMISSION); 354 355 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 356 Build.VERSION_CODES.BASE); 357 int optionalMinSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_OPTIONAL_MIN_SDK_VERSION, 358 minSdkVersion); 359 360 return new Permission(name, featureFlag, minSdkVersion, optionalMinSdkVersion); 361 } 362 363 @Nullable parseRole(@onNull XmlResourceParser parser, @NonNull ArrayMap<String, PermissionSet> permissionSets)364 private Role parseRole(@NonNull XmlResourceParser parser, 365 @NonNull ArrayMap<String, PermissionSet> permissionSets) throws IOException, 366 XmlPullParserException { 367 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_ROLE); 368 if (name == null) { 369 skipCurrentTag(parser); 370 return null; 371 } 372 373 boolean allowBypassingQualification = getAttributeBooleanValue(parser, 374 ATTRIBUTE_ALLOW_BYPASSING_QUALIFICATION, false); 375 376 String behaviorClassSimpleName = getAttributeValue(parser, ATTRIBUTE_BEHAVIOR); 377 RoleBehavior behavior; 378 if (behaviorClassSimpleName != null) { 379 String behaviorClassName = BEHAVIOR_PACKAGE_NAME + '.' + behaviorClassSimpleName; 380 try { 381 behavior = (RoleBehavior) Class.forName(behaviorClassName).newInstance(); 382 } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { 383 throwOrLogMessage("Unable to instantiate behavior: " + behaviorClassName, e); 384 skipCurrentTag(parser); 385 return null; 386 } 387 } else { 388 behavior = null; 389 } 390 391 String defaultHoldersResourceName = getAttributeValue(parser, ATTRIBUTE_DEFAULT_HOLDERS); 392 393 int descriptionResource = getAttributeResourceValue(parser, ATTRIBUTE_DESCRIPTION, 0); 394 395 boolean visible = getAttributeBooleanValue(parser, ATTRIBUTE_VISIBLE, true); 396 Integer labelResource; 397 Integer shortLabelResource; 398 if (visible) { 399 if (descriptionResource == 0) { 400 skipCurrentTag(parser); 401 return null; 402 } 403 404 labelResource = requireAttributeResourceValue(parser, ATTRIBUTE_LABEL, 0, TAG_ROLE); 405 if (labelResource == null) { 406 skipCurrentTag(parser); 407 return null; 408 } 409 410 shortLabelResource = requireAttributeResourceValue(parser, ATTRIBUTE_SHORT_LABEL, 0, 411 TAG_ROLE); 412 if (shortLabelResource == null) { 413 skipCurrentTag(parser); 414 return null; 415 } 416 } else if (behavior != null) { 417 labelResource = getAttributeResourceValue(parser, ATTRIBUTE_LABEL, 0); 418 shortLabelResource = getAttributeResourceValue(parser, ATTRIBUTE_SHORT_LABEL, 0); 419 } else { 420 labelResource = 0; 421 shortLabelResource = 0; 422 } 423 424 int exclusivity; 425 if (com.android.permission.flags.Flags.crossUserRoleEnabled()) { 426 String exclusivityName = requireAttributeValue(parser, ATTRIBUTE_EXCLUSIVITY, TAG_ROLE); 427 if (exclusivityName == null) { 428 skipCurrentTag(parser); 429 return null; 430 } 431 switch (exclusivityName) { 432 case EXCLUSIVITY_NONE: 433 exclusivity = Role.EXCLUSIVITY_NONE; 434 break; 435 case EXCLUSIVITY_USER: 436 exclusivity = Role.EXCLUSIVITY_USER; 437 break; 438 case EXCLUSIVITY_PROFILE_GROUP: 439 // EXCLUSIVITY_PROFILE behavior only available for B+ 440 // fallback to default of EXCLUSIVITY_USER 441 exclusivity = SdkLevel.isAtLeastB() 442 ? Role.EXCLUSIVITY_PROFILE_GROUP 443 : Role.EXCLUSIVITY_USER; 444 break; 445 default: 446 throwOrLogMessage("Invalid value for \"exclusivity\" on <role>: " + name 447 + ", exclusivity: " + exclusivityName); 448 skipCurrentTag(parser); 449 return null; 450 } 451 } else { 452 Boolean exclusive = 453 requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true, TAG_ROLE); 454 if (exclusive == null) { 455 skipCurrentTag(parser); 456 return null; 457 } 458 exclusivity = exclusive ? Role.EXCLUSIVITY_USER : Role.EXCLUSIVITY_NONE; 459 } 460 461 462 boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser, 463 ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false); 464 465 Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, ATTRIBUTE_FEATURE_FLAG, 466 boolean.class, sFeatureFlagFallback, TAG_ROLE); 467 468 int maxSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MAX_SDK_VERSION, 469 Build.VERSION_CODES.CUR_DEVELOPMENT); 470 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 471 Build.VERSION_CODES.BASE); 472 if (minSdkVersion > maxSdkVersion) { 473 throwOrLogMessage("minSdkVersion " + minSdkVersion 474 + " cannot be greater than maxSdkVersion " + maxSdkVersion + " for role: " 475 + name); 476 skipCurrentTag(parser); 477 return null; 478 } 479 480 boolean onlyGrantWhenAdded = getAttributeBooleanValue(parser, 481 ATTRIBUTE_ONLY_GRANT_WHEN_ADDED, false); 482 483 boolean overrideUserWhenGranting = getAttributeBooleanValue(parser, 484 ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING, false); 485 486 boolean requestable = getAttributeBooleanValue(parser, ATTRIBUTE_REQUESTABLE, visible); 487 Integer requestDescriptionResource; 488 Integer requestTitleResource; 489 if (requestable) { 490 requestDescriptionResource = requireAttributeResourceValue(parser, 491 ATTRIBUTE_REQUEST_DESCRIPTION, 0, TAG_ROLE); 492 if (requestDescriptionResource == null) { 493 skipCurrentTag(parser); 494 return null; 495 } 496 497 requestTitleResource = requireAttributeResourceValue(parser, ATTRIBUTE_REQUEST_TITLE, 0, 498 TAG_ROLE); 499 if (requestTitleResource == null) { 500 skipCurrentTag(parser); 501 return null; 502 } 503 } else { 504 requestDescriptionResource = 0; 505 requestTitleResource = 0; 506 } 507 508 int searchKeywordsResource = getAttributeResourceValue(parser, ATTRIBUTE_SEARCH_KEYWORDS, 509 0); 510 511 boolean showNone = getAttributeBooleanValue(parser, ATTRIBUTE_SHOW_NONE, false); 512 if (showNone && exclusivity == Role.EXCLUSIVITY_NONE) { 513 throwOrLogMessage("showNone=\"true\" is invalid for a non-exclusive role: " + name); 514 skipCurrentTag(parser); 515 return null; 516 } 517 518 boolean statik = getAttributeBooleanValue(parser, ATTRIBUTE_STATIC, false); 519 if (statik && (visible || requestable)) { 520 throwOrLogMessage("static=\"true\" is invalid for a visible or requestable role: " 521 + name); 522 skipCurrentTag(parser); 523 return null; 524 } 525 526 boolean systemOnly = getAttributeBooleanValue(parser, ATTRIBUTE_SYSTEM_ONLY, false); 527 528 String uiBehaviorName = getAttributeValue(parser, ATTRIBUTE_UI_BEHAVIOR); 529 530 List<RequiredComponent> requiredComponents = null; 531 List<Permission> permissions = null; 532 List<Permission> appOpPermissions = null; 533 List<AppOp> appOps = null; 534 List<PreferredActivity> preferredActivities = null; 535 536 int type; 537 int depth; 538 int innerDepth = parser.getDepth() + 1; 539 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 540 && ((depth = parser.getDepth()) >= innerDepth 541 || type != XmlResourceParser.END_TAG)) { 542 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 543 continue; 544 } 545 546 switch (parser.getName()) { 547 case TAG_REQUIRED_COMPONENTS: 548 if (requiredComponents != null) { 549 throwOrLogMessage("Duplicate <required-components> in role: " + name); 550 skipCurrentTag(parser); 551 continue; 552 } 553 requiredComponents = parseRequiredComponents(parser); 554 break; 555 case TAG_PERMISSIONS: 556 if (permissions != null) { 557 throwOrLogMessage("Duplicate <permissions> in role: " + name); 558 skipCurrentTag(parser); 559 continue; 560 } 561 permissions = parsePermissions(parser, permissionSets); 562 break; 563 case TAG_APP_OP_PERMISSIONS: 564 if (appOpPermissions != null) { 565 throwOrLogMessage("Duplicate <app-op-permissions> in role: " + name); 566 skipCurrentTag(parser); 567 continue; 568 } 569 appOpPermissions = parseAppOpPermissions(parser); 570 break; 571 case TAG_APP_OPS: 572 if (appOps != null) { 573 throwOrLogMessage("Duplicate <app-ops> in role: " + name); 574 skipCurrentTag(parser); 575 continue; 576 } 577 appOps = parseAppOps(parser); 578 break; 579 case TAG_PREFERRED_ACTIVITIES: 580 if (preferredActivities != null) { 581 throwOrLogMessage("Duplicate <preferred-activities> in role: " + name); 582 skipCurrentTag(parser); 583 continue; 584 } 585 if (exclusivity == Role.EXCLUSIVITY_PROFILE_GROUP) { 586 throwOrLogMessage("<preferred-activities> is not supported for a" 587 + " profile-group-exclusive role: " + name); 588 skipCurrentTag(parser); 589 continue; 590 } 591 preferredActivities = parsePreferredActivities(parser); 592 break; 593 default: 594 throwOrLogForUnknownTag(parser); 595 skipCurrentTag(parser); 596 } 597 } 598 599 if (requiredComponents == null) { 600 requiredComponents = Collections.emptyList(); 601 } 602 if (permissions == null) { 603 permissions = Collections.emptyList(); 604 } 605 if (appOpPermissions == null) { 606 appOpPermissions = Collections.emptyList(); 607 } 608 if (appOps == null) { 609 appOps = Collections.emptyList(); 610 } 611 if (preferredActivities == null) { 612 preferredActivities = Collections.emptyList(); 613 } 614 return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName, 615 descriptionResource, exclusivity, fallBackToDefaultHolder, featureFlag, 616 labelResource, maxSdkVersion, minSdkVersion, onlyGrantWhenAdded, 617 overrideUserWhenGranting, requestDescriptionResource, requestTitleResource, 618 requestable, searchKeywordsResource, shortLabelResource, showNone, statik, 619 systemOnly, visible, requiredComponents, permissions, appOpPermissions, appOps, 620 preferredActivities, uiBehaviorName); 621 } 622 623 @NonNull parseRequiredComponents( @onNull XmlResourceParser parser)624 private List<RequiredComponent> parseRequiredComponents( 625 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 626 List<RequiredComponent> requiredComponents = new ArrayList<>(); 627 628 int type; 629 int depth; 630 int innerDepth = parser.getDepth() + 1; 631 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 632 && ((depth = parser.getDepth()) >= innerDepth 633 || type != XmlResourceParser.END_TAG)) { 634 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 635 continue; 636 } 637 638 String name = parser.getName(); 639 switch (name) { 640 case TAG_ACTIVITY: 641 case TAG_PROVIDER: 642 case TAG_RECEIVER: 643 case TAG_SERVICE: { 644 RequiredComponent requiredComponent = parseRequiredComponent(parser, name); 645 if (requiredComponent == null) { 646 continue; 647 } 648 validateNoDuplicateElement(requiredComponent, requiredComponents, 649 "require component"); 650 requiredComponents.add(requiredComponent); 651 break; 652 } 653 default: 654 throwOrLogForUnknownTag(parser); 655 skipCurrentTag(parser); 656 } 657 } 658 659 return requiredComponents; 660 } 661 662 @Nullable parseRequiredComponent(@onNull XmlResourceParser parser, @NonNull String name)663 private RequiredComponent parseRequiredComponent(@NonNull XmlResourceParser parser, 664 @NonNull String name) throws IOException, XmlPullParserException { 665 int minTargetSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_TARGET_SDK_VERSION, 666 Build.VERSION_CODES.BASE); 667 int flags = getAttributeIntValue(parser, ATTRIBUTE_FLAGS, 0); 668 String permission = getAttributeValue(parser, ATTRIBUTE_PERMISSION); 669 int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0); 670 IntentFilterData intentFilterData = null; 671 List<RequiredMetaData> metaData = new ArrayList<>(); 672 List<String> validationMetaDataNames = mThrowOnError ? new ArrayList<>() : null; 673 674 int type; 675 int depth; 676 int innerDepth = parser.getDepth() + 1; 677 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 678 && ((depth = parser.getDepth()) >= innerDepth 679 || type != XmlResourceParser.END_TAG)) { 680 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 681 continue; 682 } 683 684 switch (parser.getName()) { 685 case TAG_INTENT_FILTER: 686 if (intentFilterData != null) { 687 throwOrLogMessage("Duplicate <intent-filter> in <" + name + ">"); 688 skipCurrentTag(parser); 689 continue; 690 } 691 intentFilterData = parseIntentFilterData(parser); 692 break; 693 case TAG_META_DATA: 694 String metaDataName = requireAttributeValue(parser, ATTRIBUTE_NAME, 695 TAG_META_DATA); 696 if (metaDataName == null) { 697 continue; 698 } 699 if (mThrowOnError) { 700 validateNoDuplicateElement(metaDataName, validationMetaDataNames, 701 "meta data"); 702 } 703 // HACK: Only support boolean for now. 704 // TODO(b/211568084): Support android:resource and other types of android:value, 705 // maybe by switching to TypedArray and styleables. 706 Boolean metaDataValue = requireAttributeBooleanValue(parser, ATTRIBUTE_VALUE, 707 false, TAG_META_DATA); 708 if (metaDataValue == null) { 709 continue; 710 } 711 boolean metaDataProhibited = getAttributeBooleanValue(parser, 712 ATTRIBUTE_PROHIBITED, false); 713 RequiredMetaData requiredMetaData = new RequiredMetaData(metaDataName, 714 metaDataValue, metaDataProhibited); 715 metaData.add(requiredMetaData); 716 if (mThrowOnError) { 717 validationMetaDataNames.add(metaDataName); 718 } 719 break; 720 default: 721 throwOrLogForUnknownTag(parser); 722 skipCurrentTag(parser); 723 } 724 } 725 726 if (intentFilterData == null) { 727 throwOrLogMessage("Missing <intent-filter> in <" + name + ">"); 728 return null; 729 } 730 switch (name) { 731 case TAG_ACTIVITY: 732 return new RequiredActivity(intentFilterData, minTargetSdkVersion, flags, 733 permission, queryFlags, metaData); 734 case TAG_PROVIDER: 735 return new RequiredContentProvider(intentFilterData, minTargetSdkVersion, flags, 736 permission, queryFlags, metaData); 737 case TAG_RECEIVER: 738 return new RequiredBroadcastReceiver(intentFilterData, minTargetSdkVersion, flags, 739 permission, queryFlags, metaData); 740 case TAG_SERVICE: 741 return new RequiredService(intentFilterData, minTargetSdkVersion, flags, permission, 742 queryFlags, metaData); 743 default: 744 throwOrLogMessage("Unknown tag <" + name + ">"); 745 return null; 746 } 747 } 748 749 @Nullable parseIntentFilterData(@onNull XmlResourceParser parser)750 private IntentFilterData parseIntentFilterData(@NonNull XmlResourceParser parser) 751 throws IOException, XmlPullParserException { 752 String action = null; 753 List<String> categories = new ArrayList<>(); 754 boolean hasData = false; 755 String dataScheme = null; 756 String dataType = null; 757 758 int type; 759 int depth; 760 int innerDepth = parser.getDepth() + 1; 761 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 762 && ((depth = parser.getDepth()) >= innerDepth 763 || type != XmlResourceParser.END_TAG)) { 764 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 765 continue; 766 } 767 768 switch (parser.getName()) { 769 case TAG_ACTION: 770 if (action != null) { 771 throwOrLogMessage("Duplicate <action> in <intent-filter>"); 772 skipCurrentTag(parser); 773 continue; 774 } 775 action = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_ACTION); 776 break; 777 case TAG_CATEGORY: { 778 String category = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_CATEGORY); 779 if (category == null) { 780 continue; 781 } 782 validateIntentFilterCategory(category); 783 validateNoDuplicateElement(category, categories, "category"); 784 categories.add(category); 785 break; 786 } 787 case TAG_DATA: 788 if (!hasData) { 789 hasData = true; 790 } else { 791 throwOrLogMessage("Duplicate <data> in <intent-filter>"); 792 skipCurrentTag(parser); 793 continue; 794 } 795 dataScheme = getAttributeValue(parser, ATTRIBUTE_SCHEME); 796 dataType = getAttributeValue(parser, ATTRIBUTE_MIME_TYPE); 797 if (dataType != null) { 798 validateIntentFilterDataType(dataType); 799 } 800 break; 801 default: 802 throwOrLogForUnknownTag(parser); 803 skipCurrentTag(parser); 804 } 805 } 806 807 if (action == null) { 808 throwOrLogMessage("Missing <action> in <intent-filter>"); 809 return null; 810 } 811 return new IntentFilterData(action, categories, dataScheme, dataType); 812 } 813 validateIntentFilterCategory(@onNull String category)814 private void validateIntentFilterCategory(@NonNull String category) { 815 if (Objects.equals(category, Intent.CATEGORY_DEFAULT)) { 816 throwOrLogMessage("<category> should not include " + Intent.CATEGORY_DEFAULT); 817 } 818 } 819 820 /** 821 * Validates the data type with the same logic in {@link 822 * android.content.IntentFilter#addDataType(String)} to prevent the {@code 823 * MalformedMimeTypeException}. 824 */ validateIntentFilterDataType(@onNull String type)825 private void validateIntentFilterDataType(@NonNull String type) { 826 int slashIndex = type.indexOf('/'); 827 if (slashIndex <= 0 || type.length() < slashIndex + 2) { 828 throwOrLogMessage("Invalid attribute \"mimeType\" value on <data>: " + type); 829 } 830 } 831 832 @NonNull parsePermissions(@onNull XmlResourceParser parser, @NonNull ArrayMap<String, PermissionSet> permissionSets)833 private List<Permission> parsePermissions(@NonNull XmlResourceParser parser, 834 @NonNull ArrayMap<String, PermissionSet> permissionSets) throws IOException, 835 XmlPullParserException { 836 List<Permission> permissions = new ArrayList<>(); 837 838 int type; 839 int depth; 840 int innerDepth = parser.getDepth() + 1; 841 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 842 && ((depth = parser.getDepth()) >= innerDepth 843 || type != XmlResourceParser.END_TAG)) { 844 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 845 continue; 846 } 847 848 switch (parser.getName()) { 849 case TAG_PERMISSION_SET: { 850 String permissionSetName = requireAttributeValue(parser, ATTRIBUTE_NAME, 851 TAG_PERMISSION_SET); 852 if (permissionSetName == null) { 853 continue; 854 } 855 PermissionSet permissionSet = permissionSets.get(permissionSetName); 856 if (permissionSet == null) { 857 throwOrLogMessage("Unknown permission set:" + permissionSetName); 858 continue; 859 } 860 Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, 861 ATTRIBUTE_FEATURE_FLAG, boolean.class, sFeatureFlagFallback, 862 TAG_PERMISSION_SET); 863 int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION, 864 Build.VERSION_CODES.BASE); 865 int optionalMinSdkVersion = getAttributeIntValue(parser, 866 ATTRIBUTE_OPTIONAL_MIN_SDK_VERSION, minSdkVersion); 867 List<Permission> permissionsInSet = permissionSet.getPermissions(); 868 int permissionsInSetSize = permissionsInSet.size(); 869 for (int permissionsInSetIndex = 0; 870 permissionsInSetIndex < permissionsInSetSize; permissionsInSetIndex++) { 871 Permission permission = permissionsInSet.get(permissionsInSetIndex); 872 Supplier<Boolean> mergedFeatureFlag = 873 mergeFeatureFlags(permission.getFeatureFlag(), featureFlag); 874 int mergedMinSdkVersion = 875 Math.max(permission.getMinSdkVersion(), minSdkVersion); 876 int mergedOptionalMinSdkVersion = Math.max( 877 permission.getOptionalMinSdkVersion(), optionalMinSdkVersion); 878 permission = permission.withFeatureFlagAndSdkVersions(mergedFeatureFlag, 879 mergedMinSdkVersion, mergedOptionalMinSdkVersion); 880 // We do allow intersection between permission sets. 881 permissions.add(permission); 882 } 883 break; 884 } 885 case TAG_PERMISSION: { 886 Permission permission = parsePermission(parser, TAG_PERMISSION); 887 if (permission == null) { 888 continue; 889 } 890 validateNoDuplicateElement(permission, permissions, "permission"); 891 permissions.add(permission); 892 break; 893 } 894 default: 895 throwOrLogForUnknownTag(parser); 896 skipCurrentTag(parser); 897 } 898 } 899 900 return permissions; 901 } 902 903 @NonNull parseAppOpPermissions(@onNull XmlResourceParser parser)904 private List<Permission> parseAppOpPermissions(@NonNull XmlResourceParser parser) 905 throws IOException, XmlPullParserException { 906 List<Permission> appOpPermissions = new ArrayList<>(); 907 908 int type; 909 int depth; 910 int innerDepth = parser.getDepth() + 1; 911 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 912 && ((depth = parser.getDepth()) >= innerDepth 913 || type != XmlResourceParser.END_TAG)) { 914 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 915 continue; 916 } 917 918 if (parser.getName().equals(TAG_APP_OP_PERMISSION)) { 919 Permission appOpPermission = parsePermission(parser, TAG_APP_OP_PERMISSION); 920 if (appOpPermission == null) { 921 continue; 922 } 923 validateNoDuplicateElement(appOpPermission, appOpPermissions, "app op permission"); 924 appOpPermissions.add(appOpPermission); 925 } else { 926 throwOrLogForUnknownTag(parser); 927 skipCurrentTag(parser); 928 } 929 } 930 931 return appOpPermissions; 932 } 933 934 @NonNull parseAppOps(@onNull XmlResourceParser parser)935 private List<AppOp> parseAppOps(@NonNull XmlResourceParser parser) throws IOException, 936 XmlPullParserException { 937 List<String> appOpNames = new ArrayList<>(); 938 List<AppOp> appOps = new ArrayList<>(); 939 940 int type; 941 int depth; 942 int innerDepth = parser.getDepth() + 1; 943 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 944 && ((depth = parser.getDepth()) >= innerDepth 945 || type != XmlResourceParser.END_TAG)) { 946 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 947 continue; 948 } 949 950 if (parser.getName().equals(TAG_APP_OP)) { 951 String name = requireAttributeValue(parser, ATTRIBUTE_NAME, TAG_APP_OP); 952 if (name == null) { 953 continue; 954 } 955 validateNoDuplicateElement(name, appOpNames, "app op"); 956 appOpNames.add(name); 957 Supplier<Boolean> featureFlag = getAttributeMethodValue(parser, 958 ATTRIBUTE_FEATURE_FLAG, boolean.class, sFeatureFlagFallback, TAG_APP_OP); 959 Integer maxTargetSdkVersion = getAttributeIntValue(parser, 960 ATTRIBUTE_MAX_TARGET_SDK_VERSION, Integer.MIN_VALUE); 961 if (maxTargetSdkVersion == Integer.MIN_VALUE) { 962 maxTargetSdkVersion = null; 963 } 964 if (maxTargetSdkVersion != null && maxTargetSdkVersion < Build.VERSION_CODES.BASE) { 965 throwOrLogMessage("Invalid value for \"maxTargetSdkVersion\": " 966 + maxTargetSdkVersion); 967 } 968 int minSdkVersion = getAttributeIntValue(parser, 969 ATTRIBUTE_MIN_SDK_VERSION, Build.VERSION_CODES.BASE); 970 String modeName = requireAttributeValue(parser, ATTRIBUTE_MODE, TAG_APP_OP); 971 if (modeName == null) { 972 continue; 973 } 974 int modeIndex = sModeNameToMode.indexOfKey(modeName); 975 if (modeIndex < 0) { 976 throwOrLogMessage("Unknown value for \"mode\" on <app-op>: " + modeName); 977 continue; 978 } 979 int mode = sModeNameToMode.valueAt(modeIndex); 980 AppOp appOp = new AppOp(name, featureFlag, maxTargetSdkVersion, minSdkVersion, 981 mode); 982 appOps.add(appOp); 983 } else { 984 throwOrLogForUnknownTag(parser); 985 skipCurrentTag(parser); 986 } 987 } 988 989 return appOps; 990 } 991 992 @NonNull parsePreferredActivities( @onNull XmlResourceParser parser)993 private List<PreferredActivity> parsePreferredActivities( 994 @NonNull XmlResourceParser parser) throws IOException, XmlPullParserException { 995 List<PreferredActivity> preferredActivities = new ArrayList<>(); 996 997 int type; 998 int depth; 999 int innerDepth = parser.getDepth() + 1; 1000 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 1001 && ((depth = parser.getDepth()) >= innerDepth 1002 || type != XmlResourceParser.END_TAG)) { 1003 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 1004 continue; 1005 } 1006 1007 if (parser.getName().equals(TAG_PREFERRED_ACTIVITY)) { 1008 PreferredActivity preferredActivity = parsePreferredActivity(parser); 1009 if (preferredActivity == null) { 1010 continue; 1011 } 1012 validateNoDuplicateElement(preferredActivity, preferredActivities, 1013 "preferred activity"); 1014 preferredActivities.add(preferredActivity); 1015 } else { 1016 throwOrLogForUnknownTag(parser); 1017 skipCurrentTag(parser); 1018 } 1019 } 1020 1021 return preferredActivities; 1022 } 1023 1024 @Nullable parsePreferredActivity(@onNull XmlResourceParser parser)1025 private PreferredActivity parsePreferredActivity(@NonNull XmlResourceParser parser) 1026 throws IOException, XmlPullParserException { 1027 RequiredActivity activity = null; 1028 List<IntentFilterData> intentFilterDatas = new ArrayList<>(); 1029 1030 int type; 1031 int depth; 1032 int innerDepth = parser.getDepth() + 1; 1033 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 1034 && ((depth = parser.getDepth()) >= innerDepth 1035 || type != XmlResourceParser.END_TAG)) { 1036 if (depth > innerDepth || type != XmlResourceParser.START_TAG) { 1037 continue; 1038 } 1039 1040 switch (parser.getName()) { 1041 case TAG_ACTIVITY: 1042 if (activity != null) { 1043 throwOrLogMessage("Duplicate <activity> in <preferred-activity>"); 1044 skipCurrentTag(parser); 1045 continue; 1046 } 1047 activity = (RequiredActivity) parseRequiredComponent(parser, TAG_ACTIVITY); 1048 break; 1049 case TAG_INTENT_FILTER: 1050 IntentFilterData intentFilterData = parseIntentFilterData(parser); 1051 if (intentFilterData == null) { 1052 continue; 1053 } 1054 validateNoDuplicateElement(intentFilterData, intentFilterDatas, 1055 "intent filter"); 1056 if (intentFilterData.getDataType() != null) { 1057 throwOrLogMessage("mimeType in <data> is not supported when setting a" 1058 + " preferred activity"); 1059 } 1060 intentFilterDatas.add(intentFilterData); 1061 break; 1062 default: 1063 throwOrLogForUnknownTag(parser); 1064 skipCurrentTag(parser); 1065 } 1066 } 1067 1068 if (activity == null) { 1069 throwOrLogMessage("Missing <activity> in <preferred-activity>"); 1070 return null; 1071 } 1072 if (intentFilterDatas.isEmpty()) { 1073 throwOrLogMessage("Missing <intent-filter> in <preferred-activity>"); 1074 return null; 1075 } 1076 return new PreferredActivity(activity, intentFilterDatas); 1077 } 1078 skipCurrentTag(@onNull XmlResourceParser parser)1079 private void skipCurrentTag(@NonNull XmlResourceParser parser) 1080 throws XmlPullParserException, IOException { 1081 int type; 1082 int innerDepth = parser.getDepth() + 1; 1083 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT 1084 && (parser.getDepth() >= innerDepth || type != XmlResourceParser.END_TAG)) { 1085 // Do nothing 1086 } 1087 } 1088 1089 @Nullable getAttributeValue(@onNull XmlResourceParser parser, @NonNull String name)1090 private String getAttributeValue(@NonNull XmlResourceParser parser, 1091 @NonNull String name) { 1092 return parser.getAttributeValue(null, name); 1093 } 1094 1095 @Nullable requireAttributeValue(@onNull XmlResourceParser parser, @NonNull String name, @NonNull String tagName)1096 private String requireAttributeValue(@NonNull XmlResourceParser parser, 1097 @NonNull String name, @NonNull String tagName) { 1098 String value = getAttributeValue(parser, name); 1099 if (value == null) { 1100 throwOrLogMessage("Missing attribute \"" + name + "\" on <" + tagName + ">"); 1101 } 1102 return value; 1103 } 1104 getAttributeBooleanValue(@onNull XmlResourceParser parser, @NonNull String name, boolean defaultValue)1105 private boolean getAttributeBooleanValue(@NonNull XmlResourceParser parser, 1106 @NonNull String name, boolean defaultValue) { 1107 return parser.getAttributeBooleanValue(null, name, defaultValue); 1108 } 1109 1110 @Nullable requireAttributeBooleanValue(@onNull XmlResourceParser parser, @NonNull String name, boolean defaultValue, @NonNull String tagName)1111 private Boolean requireAttributeBooleanValue(@NonNull XmlResourceParser parser, 1112 @NonNull String name, boolean defaultValue, @NonNull String tagName) { 1113 String value = requireAttributeValue(parser, name, tagName); 1114 if (value == null) { 1115 return null; 1116 } 1117 return getAttributeBooleanValue(parser, name, defaultValue); 1118 } 1119 getAttributeIntValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue)1120 private int getAttributeIntValue(@NonNull XmlResourceParser parser, 1121 @NonNull String name, int defaultValue) { 1122 return parser.getAttributeIntValue(null, name, defaultValue); 1123 } 1124 getAttributeResourceValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue)1125 private int getAttributeResourceValue(@NonNull XmlResourceParser parser, 1126 @NonNull String name, int defaultValue) { 1127 return parser.getAttributeResourceValue(null, name, defaultValue); 1128 } 1129 1130 @Nullable requireAttributeResourceValue(@onNull XmlResourceParser parser, @NonNull String name, int defaultValue, @NonNull String tagName)1131 private Integer requireAttributeResourceValue(@NonNull XmlResourceParser parser, 1132 @NonNull String name, int defaultValue, @NonNull String tagName) { 1133 String value = requireAttributeValue(parser, name, tagName); 1134 if (value == null) { 1135 return null; 1136 } 1137 return getAttributeResourceValue(parser, name, defaultValue); 1138 } 1139 1140 @Nullable getAttributeMethodValue(@onNull XmlResourceParser parser, @NonNull String name, @NonNull Class<T> returnType, @Nullable Supplier<T> fallbackValue, @NonNull String tagName)1141 private <T> Supplier<T> getAttributeMethodValue(@NonNull XmlResourceParser parser, 1142 @NonNull String name, @NonNull Class<T> returnType, @Nullable Supplier<T> fallbackValue, 1143 @NonNull String tagName) { 1144 String value = parser.getAttributeValue(null, name); 1145 if (value == null) { 1146 return null; 1147 } 1148 int lastDotIndex = value.lastIndexOf('.'); 1149 if (lastDotIndex == -1) { 1150 throwOrLogMessage("Invalid method \"" + value + "\" for \"" + name + "\" on <" + tagName 1151 + ">"); 1152 return fallbackValue; 1153 } 1154 String className = applyJarjarTransform(value.substring(0, lastDotIndex)); 1155 String methodName = value.substring(lastDotIndex + 1); 1156 Method method; 1157 try { 1158 Class<?> clazz = Class.forName(className); 1159 method = clazz.getMethod(methodName); 1160 } catch (Exception e) { 1161 throwOrLogMessage("Cannot find method \"" + value + "\" for \"" + name + "\" on <" 1162 + tagName + ">", e); 1163 return fallbackValue; 1164 } 1165 int methodModifiers = method.getModifiers(); 1166 if ((methodModifiers & Modifier.PUBLIC) != Modifier.PUBLIC) { 1167 throwOrLogMessage("Non-public method \"" + value + "\" for \"" + name + "\" on <" 1168 + tagName + ">"); 1169 return fallbackValue; 1170 } 1171 if ((methodModifiers & Modifier.STATIC) != Modifier.STATIC) { 1172 throwOrLogMessage("Non-static method \"" + value + "\" for \"" + name + "\" on <" 1173 + tagName + ">"); 1174 return fallbackValue; 1175 } 1176 Class<?> methodReturnType = method.getReturnType(); 1177 if (!primitiveToWrapperClass(returnType).isAssignableFrom( 1178 primitiveToWrapperClass(methodReturnType))) { 1179 throwOrLogMessage("Unexpected return type for method \"" + value + "\" for \"" 1180 + name + "\" on <" + tagName + ">, expected:" + returnType.getName() 1181 + ", found: " + methodReturnType.getName()); 1182 return fallbackValue; 1183 } 1184 return new Supplier<T>() { 1185 @Override 1186 public T get() { 1187 try { 1188 //noinspection unchecked 1189 return (T) method.invoke(null); 1190 } catch (Exception e) { 1191 throwOrLogMessage("Failed to invoke method \"" + value + "\" for \"" + name 1192 + "\" on <" + tagName + ">", e); 1193 return fallbackValue.get(); 1194 } 1195 } 1196 @Override 1197 public String toString() { 1198 return "Method{name=" + value + "}"; 1199 } 1200 }; 1201 } 1202 1203 // LINT.IfChange(applyJarjarTransform) 1204 /** 1205 * Simulate the jarjar transform that should happen on the class name. 1206 * <p> 1207 * Currently this only handles the {@code Flags} classes for feature flagging. 1208 */ 1209 @NonNull 1210 private String applyJarjarTransform(@NonNull String className) { 1211 if (className.endsWith(".Flags")) { 1212 String jarjarPrefix = Objects.equals(mContext.getPackageName(), "android") 1213 ? "com.android.permission.jarjar." : "com.android.permissioncontroller.jarjar."; 1214 return jarjarPrefix + className; 1215 } 1216 return className; 1217 } 1218 // LINT.ThenChange() 1219 1220 /** 1221 * Return the wrapper class for the given class if it's a primitive type, or the class itself 1222 * otherwise. 1223 */ 1224 @NonNull 1225 private Class<?> primitiveToWrapperClass(@NonNull Class<?> clazz) { 1226 if (clazz.isPrimitive()) { 1227 return sPrimitiveToWrapperClass.get(clazz); 1228 } 1229 return clazz; 1230 } 1231 1232 @Nullable 1233 private Supplier<Boolean> mergeFeatureFlags(@Nullable Supplier<Boolean> featureFlag1, 1234 @Nullable Supplier<Boolean> featureFlag2) { 1235 if (featureFlag1 == null) { 1236 return featureFlag2; 1237 } 1238 if (featureFlag2 == null) { 1239 return featureFlag1; 1240 } 1241 return () -> featureFlag1.get() && featureFlag2.get(); 1242 } 1243 1244 private <T> void validateNoDuplicateElement(@NonNull T element, 1245 @NonNull Collection<T> collection, @NonNull String name) { 1246 if (collection.contains(element)) { 1247 throwOrLogMessage("Duplicate " + name + ": " + element); 1248 } 1249 } 1250 1251 private void throwOrLogMessage(String message) { 1252 if (mThrowOnError) { 1253 throw new IllegalArgumentException(message); 1254 } else { 1255 Log.wtf(LOG_TAG, message); 1256 } 1257 } 1258 1259 private void throwOrLogMessage(String message, Throwable cause) { 1260 if (mThrowOnError) { 1261 throw new IllegalArgumentException(message, cause); 1262 } else { 1263 Log.wtf(LOG_TAG, message, cause); 1264 } 1265 } 1266 1267 private void throwOrLogForUnknownTag(@NonNull XmlResourceParser parser) { 1268 throwOrLogMessage("Unknown tag: " + parser.getName()); 1269 } 1270 } 1271