xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.manifest;
2 
3 import java.io.InputStream;
4 import java.nio.file.Files;
5 import java.nio.file.Path;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.HashSet;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedHashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Objects;
16 import java.util.Set;
17 import javax.annotation.Nonnull;
18 import javax.annotation.Nullable;
19 import javax.xml.parsers.DocumentBuilder;
20 import javax.xml.parsers.DocumentBuilderFactory;
21 import org.robolectric.pluginapi.UsesSdk;
22 import org.robolectric.res.Fs;
23 import org.robolectric.res.ResourcePath;
24 import org.robolectric.res.ResourceTable;
25 import org.robolectric.util.Logger;
26 import org.w3c.dom.Document;
27 import org.w3c.dom.NamedNodeMap;
28 import org.w3c.dom.Node;
29 import org.w3c.dom.NodeList;
30 
31 /**
32  * A wrapper for an Android App Manifest, which represents information about one's App to an Android
33  * system.
34  *
35  * @see <a href="https://developer.android.com/guide/topics/manifest/manifest-intro.html">Android
36  *     App Manifest</a>
37  */
38 @SuppressWarnings("NewApi")
39 public class AndroidManifest implements UsesSdk {
40   private final Path androidManifestFile;
41   private final Path resDirectory;
42   private final Path assetsDirectory;
43   private final String overridePackageName;
44   private final List<AndroidManifest> libraryManifests;
45   private final Path apkFile;
46 
47   private boolean manifestIsParsed;
48 
49   private String applicationName;
50   private String applicationLabel;
51   private String rClassName;
52   private String packageName;
53   private String processName;
54   private String themeRef;
55   private String labelRef;
56   private String appComponentFactory; // Added from SDK 28
57   private Integer minSdkVersion;
58   private Integer targetSdkVersion;
59   private Integer maxSdkVersion;
60   private int versionCode;
61   private String versionName;
62   private final Map<String, PermissionItemData> permissions = new HashMap<>();
63   private final Map<String, PermissionGroupItemData> permissionGroups = new HashMap<>();
64   private final List<ContentProviderData> providers = new ArrayList<>();
65   private final List<BroadcastReceiverData> receivers = new ArrayList<>();
66   private final Map<String, ServiceData> serviceDatas = new LinkedHashMap<>();
67   private final Map<String, ActivityData> activityDatas = new LinkedHashMap<>();
68   private final List<String> usedPermissions = new ArrayList<>();
69   private final Map<String, String> applicationAttributes = new HashMap<>();
70   private MetaData applicationMetaData;
71 
72   /**
73    * Creates a Robolectric configuration using specified locations.
74    *
75    * @param androidManifestFile Location of the AndroidManifest.xml file.
76    * @param resDirectory Location of the res directory.
77    * @param assetsDirectory Location of the assets directory.
78    */
AndroidManifest(Path androidManifestFile, Path resDirectory, Path assetsDirectory)79   public AndroidManifest(Path androidManifestFile, Path resDirectory, Path assetsDirectory) {
80     this(androidManifestFile, resDirectory, assetsDirectory, null);
81   }
82 
83   /**
84    * Creates a Robolectric configuration using specified values.
85    *
86    * @param androidManifestFile Location of the AndroidManifest.xml file.
87    * @param resDirectory Location of the res directory.
88    * @param assetsDirectory Location of the assets directory.
89    * @param overridePackageName Application package name.
90    */
AndroidManifest( Path androidManifestFile, Path resDirectory, Path assetsDirectory, String overridePackageName)91   public AndroidManifest(
92       Path androidManifestFile,
93       Path resDirectory,
94       Path assetsDirectory,
95       String overridePackageName) {
96     this(
97         androidManifestFile,
98         resDirectory,
99         assetsDirectory,
100         Collections.emptyList(),
101         overridePackageName);
102   }
103 
104   /**
105    * Creates a Robolectric configuration using specified values.
106    *
107    * @param androidManifestFile Location of the AndroidManifest.xml file.
108    * @param resDirectory Location of the res directory.
109    * @param assetsDirectory Location of the assets directory.
110    * @param libraryManifests List of dependency library manifests.
111    * @param overridePackageName Application package name.
112    */
AndroidManifest( Path androidManifestFile, Path resDirectory, Path assetsDirectory, @Nonnull List<AndroidManifest> libraryManifests, String overridePackageName)113   public AndroidManifest(
114       Path androidManifestFile,
115       Path resDirectory,
116       Path assetsDirectory,
117       @Nonnull List<AndroidManifest> libraryManifests,
118       String overridePackageName) {
119     this(
120         androidManifestFile,
121         resDirectory,
122         assetsDirectory,
123         libraryManifests,
124         overridePackageName,
125         null);
126   }
127 
AndroidManifest( Path androidManifestFile, Path resDirectory, Path assetsDirectory, @Nonnull List<AndroidManifest> libraryManifests, String overridePackageName, Path apkFile)128   public AndroidManifest(
129       Path androidManifestFile,
130       Path resDirectory,
131       Path assetsDirectory,
132       @Nonnull List<AndroidManifest> libraryManifests,
133       String overridePackageName,
134       Path apkFile) {
135     this.androidManifestFile = androidManifestFile;
136     this.resDirectory = resDirectory;
137     this.assetsDirectory = assetsDirectory;
138     this.overridePackageName = overridePackageName;
139     this.libraryManifests = libraryManifests;
140 
141     this.packageName = overridePackageName;
142     this.apkFile = apkFile;
143   }
144 
getThemeRef(String activityClassName)145   public String getThemeRef(String activityClassName) {
146     ActivityData activityData = getActivityData(activityClassName);
147     String themeRef = activityData != null ? activityData.getThemeRef() : null;
148     if (themeRef == null) {
149       themeRef = getThemeRef();
150     }
151     return themeRef;
152   }
153 
getRClassName()154   public String getRClassName() throws Exception {
155     parseAndroidManifest();
156     return rClassName;
157   }
158 
getRClass()159   public Class getRClass() {
160     try {
161       String rClassName = getRClassName();
162       return Class.forName(rClassName);
163     } catch (Exception e) {
164       return null;
165     }
166   }
167 
168   @SuppressWarnings("CatchAndPrintStackTrace")
parseAndroidManifest()169   void parseAndroidManifest() {
170     if (manifestIsParsed) {
171       return;
172     }
173 
174     Logger.debug("Manifest file location: " + androidManifestFile);
175 
176     if (androidManifestFile != null && Files.exists(androidManifestFile)) {
177       try {
178         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
179 
180         DocumentBuilder db = dbf.newDocumentBuilder();
181         InputStream inputStream = Fs.getInputStream(androidManifestFile);
182         Document manifestDocument = db.parse(inputStream);
183         inputStream.close();
184 
185         Logger.debug("Manifest doc location:\n%s", androidManifestFile.toString());
186 
187         if (!packageNameIsOverridden()) {
188           packageName = getTagAttributeText(manifestDocument, "manifest", "package");
189         }
190 
191         versionCode =
192             getTagAttributeIntValue(manifestDocument, "manifest", "android:versionCode", 0);
193         versionName = getTagAttributeText(manifestDocument, "manifest", "android:versionName");
194         rClassName = packageName + ".R";
195 
196         Node applicationNode = findApplicationNode(manifestDocument);
197         // Parse application node of the AndroidManifest.xml
198         if (applicationNode != null) {
199           NamedNodeMap attributes = applicationNode.getAttributes();
200           int attrCount = attributes.getLength();
201           for (int i = 0; i < attrCount; i++) {
202             Node attr = attributes.item(i);
203             applicationAttributes.put(attr.getNodeName(), attr.getTextContent());
204           }
205 
206           applicationName = applicationAttributes.get("android:name");
207           applicationLabel = applicationAttributes.get("android:label");
208           processName = applicationAttributes.get("android:process");
209           themeRef = applicationAttributes.get("android:theme");
210           labelRef = applicationAttributes.get("android:label");
211           appComponentFactory = applicationAttributes.get("android:appComponentFactory");
212 
213           parseReceivers(applicationNode);
214           parseServices(applicationNode);
215           parseActivities(applicationNode);
216           parseApplicationMetaData(applicationNode);
217           parseContentProviders(applicationNode);
218         }
219 
220         minSdkVersion =
221             getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:minSdkVersion");
222 
223         String targetSdkText =
224             getTagAttributeText(manifestDocument, "uses-sdk", "android:targetSdkVersion");
225         if (targetSdkText != null) {
226           targetSdkVersion = Integer.parseInt(targetSdkText);
227         }
228 
229         maxSdkVersion =
230             getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:maxSdkVersion");
231         if (processName == null) {
232           processName = packageName;
233         }
234 
235         parseUsedPermissions(manifestDocument);
236         parsePermissions(manifestDocument);
237         parsePermissionGroups(manifestDocument);
238       } catch (Exception ignored) {
239         ignored.printStackTrace();
240       }
241     } else {
242       if (androidManifestFile != null) {
243         System.out.println("WARNING: No manifest file found at " + androidManifestFile + ".");
244         System.out.println("Falling back to the Android OS resources only.");
245         System.out.println(
246             "To remove this warning, annotate your test class with @Config(manifest=Config.NONE).");
247         System.out.println(
248             "If you're using Android Gradle Plugin, add "
249                 + "testOptions.unitTests.includeAndroidResources = true to your build.gradle");
250       }
251 
252       if (packageName == null || packageName.equals("")) {
253         packageName = "org.robolectric.default";
254       }
255 
256       rClassName = packageName + ".R";
257     }
258 
259     manifestIsParsed = true;
260   }
261 
packageNameIsOverridden()262   private boolean packageNameIsOverridden() {
263     return overridePackageName != null && !overridePackageName.isEmpty();
264   }
265 
parseUsedPermissions(Document manifestDocument)266   private void parseUsedPermissions(Document manifestDocument) {
267     NodeList elementsByTagName = manifestDocument.getElementsByTagName("uses-permission");
268     int length = elementsByTagName.getLength();
269     for (int i = 0; i < length; i++) {
270       Node node = elementsByTagName.item(i).getAttributes().getNamedItem("android:name");
271       usedPermissions.add(node.getNodeValue());
272     }
273   }
274 
parsePermissions(final Document manifestDocument)275   private void parsePermissions(final Document manifestDocument) {
276     NodeList elementsByTagName = manifestDocument.getElementsByTagName("permission");
277 
278     for (int i = 0; i < elementsByTagName.getLength(); i++) {
279       Node permissionNode = elementsByTagName.item(i);
280       final MetaData metaData = new MetaData(getChildrenTags(permissionNode, "meta-data"));
281       String name = getAttributeValue(permissionNode, "android:name");
282       permissions.put(
283           name,
284           new PermissionItemData(
285               name,
286               getAttributeValue(permissionNode, "android:label"),
287               getAttributeValue(permissionNode, "android:description"),
288               getAttributeValue(permissionNode, "android:permissionGroup"),
289               getAttributeValue(permissionNode, "android:protectionLevel"),
290               metaData));
291     }
292   }
293 
parsePermissionGroups(final Document manifestDocument)294   private void parsePermissionGroups(final Document manifestDocument) {
295     NodeList elementsByTagName = manifestDocument.getElementsByTagName("permission-group");
296 
297     for (int i = 0; i < elementsByTagName.getLength(); i++) {
298       Node permissionGroupNode = elementsByTagName.item(i);
299       final MetaData metaData = new MetaData(getChildrenTags(permissionGroupNode, "meta-data"));
300       String name = getAttributeValue(permissionGroupNode, "android:name");
301       permissionGroups.put(
302           name,
303           new PermissionGroupItemData(
304               name,
305               getAttributeValue(permissionGroupNode, "android:label"),
306               getAttributeValue(permissionGroupNode, "android:description"),
307               metaData));
308     }
309   }
310 
parseContentProviders(Node applicationNode)311   private void parseContentProviders(Node applicationNode) {
312     for (Node contentProviderNode : getChildrenTags(applicationNode, "provider")) {
313       String name = getAttributeValue(contentProviderNode, "android:name");
314       String authorities = getAttributeValue(contentProviderNode, "android:authorities");
315       MetaData metaData = new MetaData(getChildrenTags(contentProviderNode, "meta-data"));
316 
317       List<PathPermissionData> pathPermissionDatas = new ArrayList<>();
318       for (Node node : getChildrenTags(contentProviderNode, "path-permission")) {
319         pathPermissionDatas.add(
320             new PathPermissionData(
321                 getAttributeValue(node, "android:path"),
322                 getAttributeValue(node, "android:pathPrefix"),
323                 getAttributeValue(node, "android:pathPattern"),
324                 getAttributeValue(node, "android:readPermission"),
325                 getAttributeValue(node, "android:writePermission")));
326       }
327 
328       providers.add(
329           new ContentProviderData(
330               resolveClassRef(name),
331               metaData,
332               authorities,
333               parseNodeAttributes(contentProviderNode),
334               pathPermissionDatas));
335     }
336   }
337 
getAttributeValue(Node parentNode, String attributeName)338   private @Nullable String getAttributeValue(Node parentNode, String attributeName) {
339     Node attributeNode = parentNode.getAttributes().getNamedItem(attributeName);
340     return attributeNode == null ? null : attributeNode.getTextContent();
341   }
342 
parseNodeAttributes(Node node)343   private static HashMap<String, String> parseNodeAttributes(Node node) {
344     final NamedNodeMap attributes = node.getAttributes();
345     final int attrCount = attributes.getLength();
346     final HashMap<String, String> receiverAttrs = new HashMap<>(attributes.getLength());
347     for (int i = 0; i < attrCount; i++) {
348       Node attribute = attributes.item(i);
349       String value = attribute.getNodeValue();
350       if (value != null) {
351         receiverAttrs.put(attribute.getNodeName(), value);
352       }
353     }
354     return receiverAttrs;
355   }
356 
parseReceivers(Node applicationNode)357   private void parseReceivers(Node applicationNode) {
358     for (Node receiverNode : getChildrenTags(applicationNode, "receiver")) {
359       final HashMap<String, String> receiverAttrs = parseNodeAttributes(receiverNode);
360 
361       String receiverName = resolveClassRef(receiverAttrs.get("android:name"));
362       receiverAttrs.put("android:name", receiverName);
363 
364       MetaData metaData = new MetaData(getChildrenTags(receiverNode, "meta-data"));
365 
366       final List<IntentFilterData> intentFilterData = parseIntentFilters(receiverNode);
367       BroadcastReceiverData receiver =
368           new BroadcastReceiverData(receiverAttrs, metaData, intentFilterData);
369       List<Node> intentFilters = getChildrenTags(receiverNode, "intent-filter");
370       for (Node intentFilterNode : intentFilters) {
371         for (Node actionNode : getChildrenTags(intentFilterNode, "action")) {
372           Node nameNode = actionNode.getAttributes().getNamedItem("android:name");
373           if (nameNode != null) {
374             receiver.addAction(nameNode.getTextContent());
375           }
376         }
377       }
378 
379       receivers.add(receiver);
380     }
381   }
382 
parseServices(Node applicationNode)383   private void parseServices(Node applicationNode) {
384     for (Node serviceNode : getChildrenTags(applicationNode, "service")) {
385       final HashMap<String, String> serviceAttrs = parseNodeAttributes(serviceNode);
386 
387       String serviceName = resolveClassRef(serviceAttrs.get("android:name"));
388       serviceAttrs.put("android:name", serviceName);
389 
390       MetaData metaData = new MetaData(getChildrenTags(serviceNode, "meta-data"));
391 
392       final List<IntentFilterData> intentFilterData = parseIntentFilters(serviceNode);
393       ServiceData service = new ServiceData(serviceAttrs, metaData, intentFilterData);
394       List<Node> intentFilters = getChildrenTags(serviceNode, "intent-filter");
395       for (Node intentFilterNode : intentFilters) {
396         for (Node actionNode : getChildrenTags(intentFilterNode, "action")) {
397           Node nameNode = actionNode.getAttributes().getNamedItem("android:name");
398           if (nameNode != null) {
399             service.addAction(nameNode.getTextContent());
400           }
401         }
402       }
403 
404       serviceDatas.put(serviceName, service);
405     }
406   }
407 
parseActivities(Node applicationNode)408   private void parseActivities(Node applicationNode) {
409     for (Node activityNode : getChildrenTags(applicationNode, "activity")) {
410       parseActivity(activityNode, false);
411     }
412 
413     for (Node activityNode : getChildrenTags(applicationNode, "activity-alias")) {
414       parseActivity(activityNode, true);
415     }
416   }
417 
findApplicationNode(Document manifestDocument)418   private Node findApplicationNode(Document manifestDocument) {
419     NodeList applicationNodes = manifestDocument.getElementsByTagName("application");
420     if (applicationNodes.getLength() > 1) {
421       throw new RuntimeException("found " + applicationNodes.getLength() + " application elements");
422     }
423     return applicationNodes.item(0);
424   }
425 
parseActivity(Node activityNode, boolean isAlias)426   private void parseActivity(Node activityNode, boolean isAlias) {
427     final List<IntentFilterData> intentFilterData = parseIntentFilters(activityNode);
428     final MetaData metaData = new MetaData(getChildrenTags(activityNode, "meta-data"));
429     final HashMap<String, String> activityAttrs = parseNodeAttributes(activityNode);
430 
431     String activityName = resolveClassRef(activityAttrs.get(ActivityData.getNameAttr("android")));
432     if (activityName == null) {
433       return;
434     }
435     ActivityData targetActivity = null;
436     if (isAlias) {
437       String targetName = resolveClassRef(activityAttrs.get(ActivityData.getTargetAttr("android")));
438       if (activityName == null) {
439         return;
440       }
441       // The target activity should have been parsed already so if it exists we should find it in
442       // activityDatas.
443       targetActivity = activityDatas.get(targetName);
444       activityAttrs.put(ActivityData.getTargetAttr("android"), targetName);
445     }
446     activityAttrs.put(ActivityData.getNameAttr("android"), activityName);
447     activityDatas.put(
448         activityName,
449         new ActivityData("android", activityAttrs, intentFilterData, targetActivity, metaData));
450   }
451 
parseIntentFilters(final Node activityNode)452   private List<IntentFilterData> parseIntentFilters(final Node activityNode) {
453     ArrayList<IntentFilterData> intentFilterDatas = new ArrayList<>();
454     for (Node n : getChildrenTags(activityNode, "intent-filter")) {
455       ArrayList<String> actionNames = new ArrayList<>();
456       ArrayList<String> categories = new ArrayList<>();
457       // should only be one action.
458       for (Node action : getChildrenTags(n, "action")) {
459         NamedNodeMap attributes = action.getAttributes();
460         Node actionNameNode = attributes.getNamedItem("android:name");
461         if (actionNameNode != null) {
462           actionNames.add(actionNameNode.getNodeValue());
463         }
464       }
465       for (Node category : getChildrenTags(n, "category")) {
466         NamedNodeMap attributes = category.getAttributes();
467         Node categoryNameNode = attributes.getNamedItem("android:name");
468         if (categoryNameNode != null) {
469           categories.add(categoryNameNode.getNodeValue());
470         }
471       }
472       IntentFilterData intentFilterData = new IntentFilterData(actionNames, categories);
473       intentFilterData = parseIntentFilterData(n, intentFilterData);
474       intentFilterDatas.add(intentFilterData);
475     }
476 
477     return intentFilterDatas;
478   }
479 
parseIntentFilterData( final Node intentFilterNode, IntentFilterData intentFilterData)480   private IntentFilterData parseIntentFilterData(
481       final Node intentFilterNode, IntentFilterData intentFilterData) {
482     for (Node n : getChildrenTags(intentFilterNode, "data")) {
483       NamedNodeMap attributes = n.getAttributes();
484       String host = null;
485       String port = null;
486 
487       Node schemeNode = attributes.getNamedItem("android:scheme");
488       if (schemeNode != null) {
489         intentFilterData.addScheme(schemeNode.getNodeValue());
490       }
491 
492       Node hostNode = attributes.getNamedItem("android:host");
493       if (hostNode != null) {
494         host = hostNode.getNodeValue();
495       }
496 
497       Node portNode = attributes.getNamedItem("android:port");
498       if (portNode != null) {
499         port = portNode.getNodeValue();
500       }
501       intentFilterData.addAuthority(host, port);
502 
503       Node pathNode = attributes.getNamedItem("android:path");
504       if (pathNode != null) {
505         intentFilterData.addPath(pathNode.getNodeValue());
506       }
507 
508       Node pathPatternNode = attributes.getNamedItem("android:pathPattern");
509       if (pathPatternNode != null) {
510         intentFilterData.addPathPattern(pathPatternNode.getNodeValue());
511       }
512 
513       Node pathPrefixNode = attributes.getNamedItem("android:pathPrefix");
514       if (pathPrefixNode != null) {
515         intentFilterData.addPathPrefix(pathPrefixNode.getNodeValue());
516       }
517 
518       Node mimeTypeNode = attributes.getNamedItem("android:mimeType");
519       if (mimeTypeNode != null) {
520         intentFilterData.addMimeType(mimeTypeNode.getNodeValue());
521       }
522     }
523     return intentFilterData;
524   }
525 
526   /***
527    * Allows ShadowPackageManager to provide
528    * a resource index for initialising the resource attributes in all the metadata elements
529    * @param resourceTable used for getting resource IDs from string identifiers
530    */
initMetaData(ResourceTable resourceTable)531   public void initMetaData(ResourceTable resourceTable) throws RoboNotFoundException {
532     if (!packageNameIsOverridden()) {
533       // packageName needs to be resolved
534       parseAndroidManifest();
535     }
536 
537     if (applicationMetaData != null) {
538       applicationMetaData.init(resourceTable, packageName);
539     }
540     for (PackageItemData receiver : receivers) {
541       receiver.getMetaData().init(resourceTable, packageName);
542     }
543     for (ServiceData service : serviceDatas.values()) {
544       service.getMetaData().init(resourceTable, packageName);
545     }
546     for (ContentProviderData providerData : providers) {
547       providerData.getMetaData().init(resourceTable, packageName);
548     }
549   }
550 
parseApplicationMetaData(Node applicationNode)551   private void parseApplicationMetaData(Node applicationNode) {
552     applicationMetaData = new MetaData(getChildrenTags(applicationNode, "meta-data"));
553   }
554 
resolveClassRef(String maybePartialClassName)555   private String resolveClassRef(String maybePartialClassName) {
556     return maybePartialClassName.startsWith(".")
557         ? packageName + maybePartialClassName
558         : maybePartialClassName;
559   }
560 
getChildrenTags(final Node node, final String tagName)561   private List<Node> getChildrenTags(final Node node, final String tagName) {
562     List<Node> children = new ArrayList<>();
563     for (int i = 0; i < node.getChildNodes().getLength(); i++) {
564       Node childNode = node.getChildNodes().item(i);
565       if (childNode.getNodeName().equalsIgnoreCase(tagName)) {
566         children.add(childNode);
567       }
568     }
569     return children;
570   }
571 
getTagAttributeIntValue( final Document doc, final String tag, final String attribute)572   private Integer getTagAttributeIntValue(
573       final Document doc, final String tag, final String attribute) {
574     return getTagAttributeIntValue(doc, tag, attribute, null);
575   }
576 
getTagAttributeIntValue( final Document doc, final String tag, final String attribute, final Integer defaultValue)577   private Integer getTagAttributeIntValue(
578       final Document doc, final String tag, final String attribute, final Integer defaultValue) {
579     String valueString = getTagAttributeText(doc, tag, attribute);
580     if (valueString != null) {
581       return Integer.parseInt(valueString);
582     }
583     return defaultValue;
584   }
585 
getApplicationName()586   public String getApplicationName() {
587     parseAndroidManifest();
588     return applicationName;
589   }
590 
getActivityLabel(String activityClassName)591   public String getActivityLabel(String activityClassName) {
592     parseAndroidManifest();
593     ActivityData data = getActivityData(activityClassName);
594     return (data != null && data.getLabel() != null) ? data.getLabel() : applicationLabel;
595   }
596 
getPackageName()597   public String getPackageName() {
598     parseAndroidManifest();
599     return packageName;
600   }
601 
getVersionCode()602   public int getVersionCode() {
603     return versionCode;
604   }
605 
getVersionName()606   public String getVersionName() {
607     return versionName;
608   }
609 
getLabelRef()610   public String getLabelRef() {
611     return labelRef;
612   }
613 
getAppComponentFactory()614   public String getAppComponentFactory() {
615     parseAndroidManifest();
616     return appComponentFactory;
617   }
618 
619   /**
620    * Returns the minimum Android SDK version that this package expects to be runnable on, as
621    * specified in the manifest.
622    *
623    * <p>Note that if {@link #targetSdkVersion} isn't set, this value changes the behavior of some
624    * Android code (notably {@link android.content.SharedPreferences}) to emulate old bugs.
625    *
626    * @return the minimum SDK version, or Lollipop (21) by default
627    */
628   @Override
getMinSdkVersion()629   public int getMinSdkVersion() {
630     parseAndroidManifest();
631     return minSdkVersion == null ? 21 : minSdkVersion;
632   }
633 
634   /**
635    * Returns the Android SDK version that this package prefers to be run on, as specified in the
636    * manifest.
637    *
638    * <p>Note that this value changes the behavior of some Android code (notably {@link
639    * android.content.SharedPreferences}) to emulate old bugs.
640    *
641    * @return the target SDK version, or Lollipop (21) by default
642    */
643   @Override
getTargetSdkVersion()644   public int getTargetSdkVersion() {
645     parseAndroidManifest();
646     return targetSdkVersion == null ? getMinSdkVersion() : targetSdkVersion;
647   }
648 
649   @Override
getMaxSdkVersion()650   public Integer getMaxSdkVersion() {
651     parseAndroidManifest();
652     return maxSdkVersion;
653   }
654 
getApplicationAttributes()655   public Map<String, String> getApplicationAttributes() {
656     parseAndroidManifest();
657     return applicationAttributes;
658   }
659 
getProcessName()660   public String getProcessName() {
661     parseAndroidManifest();
662     return processName;
663   }
664 
getApplicationMetaData()665   public Map<String, Object> getApplicationMetaData() {
666     parseAndroidManifest();
667     if (applicationMetaData == null) {
668       applicationMetaData = new MetaData(Collections.<Node>emptyList());
669     }
670     return applicationMetaData.getValueMap();
671   }
672 
getResourcePath()673   public ResourcePath getResourcePath() {
674     return new ResourcePath(getRClass(), resDirectory, assetsDirectory);
675   }
676 
getIncludedResourcePaths()677   public List<ResourcePath> getIncludedResourcePaths() {
678     Collection<ResourcePath> resourcePaths =
679         new LinkedHashSet<>(); // Needs stable ordering and no duplicates
680     resourcePaths.add(getResourcePath());
681     for (AndroidManifest libraryManifest : getLibraryManifests()) {
682       resourcePaths.addAll(libraryManifest.getIncludedResourcePaths());
683     }
684     return new ArrayList<>(resourcePaths);
685   }
686 
getContentProviders()687   public List<ContentProviderData> getContentProviders() {
688     parseAndroidManifest();
689     return providers;
690   }
691 
getLibraryManifests()692   public List<AndroidManifest> getLibraryManifests() {
693     assert (libraryManifests != null);
694     return Collections.unmodifiableList(libraryManifests);
695   }
696 
697   /**
698    * Returns all transitively reachable manifests, including this one, in order and without
699    * duplicates.
700    */
getAllManifests()701   public List<AndroidManifest> getAllManifests() {
702     Set<AndroidManifest> seenManifests = new HashSet<>();
703     List<AndroidManifest> uniqueManifests = new ArrayList<>();
704     addTransitiveManifests(seenManifests, uniqueManifests);
705     return uniqueManifests;
706   }
707 
addTransitiveManifests(Set<AndroidManifest> unique, List<AndroidManifest> list)708   private void addTransitiveManifests(Set<AndroidManifest> unique, List<AndroidManifest> list) {
709     if (unique.add(this)) {
710       list.add(this);
711       for (AndroidManifest androidManifest : getLibraryManifests()) {
712         androidManifest.addTransitiveManifests(unique, list);
713       }
714     }
715   }
716 
getResDirectory()717   public Path getResDirectory() {
718     return resDirectory;
719   }
720 
getAssetsDirectory()721   public Path getAssetsDirectory() {
722     return assetsDirectory;
723   }
724 
getAndroidManifestFile()725   public Path getAndroidManifestFile() {
726     return androidManifestFile;
727   }
728 
getBroadcastReceivers()729   public List<BroadcastReceiverData> getBroadcastReceivers() {
730     parseAndroidManifest();
731     return receivers;
732   }
733 
getServices()734   public List<ServiceData> getServices() {
735     parseAndroidManifest();
736     return new ArrayList<>(serviceDatas.values());
737   }
738 
getServiceData(String serviceClassName)739   public ServiceData getServiceData(String serviceClassName) {
740     parseAndroidManifest();
741     return serviceDatas.get(serviceClassName);
742   }
743 
getTagAttributeText( final Document doc, final String tag, final String attribute)744   private static String getTagAttributeText(
745       final Document doc, final String tag, final String attribute) {
746     NodeList elementsByTagName = doc.getElementsByTagName(tag);
747     for (int i = 0; i < elementsByTagName.getLength(); ++i) {
748       Node item = elementsByTagName.item(i);
749       Node namedItem = item.getAttributes().getNamedItem(attribute);
750       if (namedItem != null) {
751         return namedItem.getTextContent();
752       }
753     }
754     return null;
755   }
756 
757   @Override
equals(Object o)758   public boolean equals(Object o) {
759     if (this == o) {
760       return true;
761     }
762     if (!(o instanceof AndroidManifest)) {
763       return false;
764     }
765 
766     AndroidManifest that = (AndroidManifest) o;
767 
768     if (!Objects.equals(androidManifestFile, that.androidManifestFile)) {
769       return false;
770     }
771     if (!Objects.equals(resDirectory, that.resDirectory)) {
772       return false;
773     }
774     if (!Objects.equals(assetsDirectory, that.assetsDirectory)) {
775       return false;
776     }
777     if (!Objects.equals(overridePackageName, that.overridePackageName)) {
778       return false;
779     }
780     if (!Objects.equals(libraryManifests, that.libraryManifests)) {
781       return false;
782     }
783     return Objects.equals(apkFile, that.apkFile);
784   }
785 
786   @Override
hashCode()787   public int hashCode() {
788     int result = androidManifestFile != null ? androidManifestFile.hashCode() : 0;
789     result = 31 * result + (resDirectory != null ? resDirectory.hashCode() : 0);
790     result = 31 * result + (assetsDirectory != null ? assetsDirectory.hashCode() : 0);
791     result = 31 * result + (overridePackageName != null ? overridePackageName.hashCode() : 0);
792     result = 31 * result + (libraryManifests != null ? libraryManifests.hashCode() : 0);
793     result = 31 * result + (apkFile != null ? apkFile.hashCode() : 0);
794     return result;
795   }
796 
getActivityData(String activityClassName)797   public ActivityData getActivityData(String activityClassName) {
798     parseAndroidManifest();
799     return activityDatas.get(activityClassName);
800   }
801 
getThemeRef()802   public String getThemeRef() {
803     return themeRef;
804   }
805 
getActivityDatas()806   public Map<String, ActivityData> getActivityDatas() {
807     parseAndroidManifest();
808     return activityDatas;
809   }
810 
getUsedPermissions()811   public List<String> getUsedPermissions() {
812     parseAndroidManifest();
813     return usedPermissions;
814   }
815 
getPermissions()816   public Map<String, PermissionItemData> getPermissions() {
817     parseAndroidManifest();
818     return permissions;
819   }
820 
getPermissionGroups()821   public Map<String, PermissionGroupItemData> getPermissionGroups() {
822     parseAndroidManifest();
823     return permissionGroups;
824   }
825 
826   /**
827    * Returns data for the broadcast receiver with the provided name from this manifest. If no
828    * receiver with the class name can be found, returns null.
829    *
830    * @param className the fully resolved class name of the receiver
831    * @return data for the receiver or null if it cannot be found
832    */
getBroadcastReceiver(String className)833   public @Nullable BroadcastReceiverData getBroadcastReceiver(String className) {
834     parseAndroidManifest();
835     for (BroadcastReceiverData receiver : receivers) {
836       if (receiver.getName().equals(className)) {
837         return receiver;
838       }
839     }
840     return null;
841   }
842 
getApkFile()843   public Path getApkFile() {
844     return apkFile;
845   }
846 
847   /**
848    * @deprecated Do not use.
849    */
850   @Deprecated
851   @SuppressWarnings("InlineMeSuggester")
supportsLegacyResourcesMode()852   public final boolean supportsLegacyResourcesMode() {
853     return true;
854   }
855 
856   /**
857    * @deprecated Do not use.
858    */
859   @Deprecated
supportsBinaryResourcesMode()860   public synchronized boolean supportsBinaryResourcesMode() {
861     return true;
862   }
863 }
864