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