Home | History | Annotate | Download | only in manifest
      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    */
     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    */
     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    */
     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 
    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 
    114   public String getRClassName() throws Exception {
    115     parseAndroidManifest();
    116     return rClassName;
    117   }
    118 
    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 
    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 
    213   private boolean packageNameIsOverridden() {
    214     return overridePackageName != null && !overridePackageName.isEmpty();
    215   }
    216 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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    */
    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 
    501   private void parseApplicationMetaData(Node applicationNode) {
    502     applicationMetaData = new MetaData(getChildrenTags(applicationNode, "meta-data"));
    503   }
    504 
    505   private String resolveClassRef(String maybePartialClassName) {
    506     return (maybePartialClassName.startsWith(".")) ? packageName + maybePartialClassName : maybePartialClassName;
    507   }
    508 
    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 
    520   private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute) {
    521     return getTagAttributeIntValue(doc, tag, attribute, null);
    522   }
    523 
    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 
    532   public String getApplicationName() {
    533     parseAndroidManifest();
    534     return applicationName;
    535   }
    536 
    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 
    543   public String getPackageName() {
    544     parseAndroidManifest();
    545     return packageName;
    546   }
    547 
    548   public int getVersionCode() {
    549     return versionCode;
    550   }
    551 
    552   public String getVersionName() {
    553     return versionName;
    554   }
    555 
    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    */
    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    */
    583   public int getTargetSdkVersion() {
    584     parseAndroidManifest();
    585     return targetSdkVersion == null ? getMinSdkVersion() : targetSdkVersion;
    586   }
    587 
    588   public Integer getMaxSdkVersion() {
    589     parseAndroidManifest();
    590     return maxSdkVersion;
    591   }
    592 
    593   public Map<String, String> getApplicationAttributes() {
    594     parseAndroidManifest();
    595     return applicationAttributes;
    596   }
    597 
    598   public String getProcessName() {
    599     parseAndroidManifest();
    600     return processName;
    601   }
    602 
    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 
    611   public ResourcePath getResourcePath() {
    612     return new ResourcePath(getRClass(), resDirectory, assetsDirectory);
    613   }
    614 
    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 
    624   public List<ContentProviderData> getContentProviders() {
    625     parseAndroidManifest();
    626     return providers;
    627   }
    628 
    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    */
    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 
    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 
    654   public FsFile getResDirectory() {
    655     return resDirectory;
    656   }
    657 
    658   public FsFile getAssetsDirectory() {
    659     return assetsDirectory;
    660   }
    661 
    662   public FsFile getAndroidManifestFile() {
    663     return androidManifestFile;
    664   }
    665 
    666   public List<BroadcastReceiverData> getBroadcastReceivers() {
    667     parseAndroidManifest();
    668     return receivers;
    669   }
    670 
    671   public List<ServiceData> getServices() {
    672     parseAndroidManifest();
    673     return new ArrayList<>(serviceDatas.values());
    674   }
    675 
    676   public ServiceData getServiceData(String serviceClassName) {
    677     parseAndroidManifest();
    678     return serviceDatas.get(serviceClassName);
    679   }
    680 
    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
    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
    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 
    718   public ActivityData getActivityData(String activityClassName) {
    719     parseAndroidManifest();
    720     return activityDatas.get(activityClassName);
    721   }
    722 
    723   public String getThemeRef() {
    724     return themeRef;
    725   }
    726 
    727   public Map<String, ActivityData> getActivityDatas() {
    728     parseAndroidManifest();
    729     return activityDatas;
    730   }
    731 
    732   public List<String> getUsedPermissions() {
    733     parseAndroidManifest();
    734     return usedPermissions;
    735   }
    736 
    737   public Map<String, PermissionItemData> getPermissions() {
    738     parseAndroidManifest();
    739     return permissions;
    740   }
    741 
    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    */
    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