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