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