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