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