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