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