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