• 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.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