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.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    */
     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    */
     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    */
     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 
    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 
    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 
    130   public String getRClassName() throws Exception {
    131     parseAndroidManifest();
    132     return rClassName;
    133   }
    134 
    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")
    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 
    230   private boolean packageNameIsOverridden() {
    231     return overridePackageName != null && !overridePackageName.isEmpty();
    232   }
    233 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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    */
    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 
    516   private void parseApplicationMetaData(Node applicationNode) {
    517     applicationMetaData = new MetaData(getChildrenTags(applicationNode, "meta-data"));
    518   }
    519 
    520   private String resolveClassRef(String maybePartialClassName) {
    521     return (maybePartialClassName.startsWith(".")) ? packageName + maybePartialClassName : maybePartialClassName;
    522   }
    523 
    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 
    535   private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute) {
    536     return getTagAttributeIntValue(doc, tag, attribute, null);
    537   }
    538 
    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 
    547   public String getApplicationName() {
    548     parseAndroidManifest();
    549     return applicationName;
    550   }
    551 
    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 
    558   public String getPackageName() {
    559     parseAndroidManifest();
    560     return packageName;
    561   }
    562 
    563   public int getVersionCode() {
    564     return versionCode;
    565   }
    566 
    567   public String getVersionName() {
    568     return versionName;
    569   }
    570 
    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
    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
    600   public int getTargetSdkVersion() {
    601     parseAndroidManifest();
    602     return targetSdkVersion == null ? getMinSdkVersion() : targetSdkVersion;
    603   }
    604 
    605   @Override
    606   public Integer getMaxSdkVersion() {
    607     parseAndroidManifest();
    608     return maxSdkVersion;
    609   }
    610 
    611   public Map<String, String> getApplicationAttributes() {
    612     parseAndroidManifest();
    613     return applicationAttributes;
    614   }
    615 
    616   public String getProcessName() {
    617     parseAndroidManifest();
    618     return processName;
    619   }
    620 
    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 
    629   public ResourcePath getResourcePath() {
    630     return new ResourcePath(getRClass(), resDirectory, assetsDirectory);
    631   }
    632 
    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 
    642   public List<ContentProviderData> getContentProviders() {
    643     parseAndroidManifest();
    644     return providers;
    645   }
    646 
    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    */
    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 
    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 
    672   public FsFile getResDirectory() {
    673     return resDirectory;
    674   }
    675 
    676   public FsFile getAssetsDirectory() {
    677     return assetsDirectory;
    678   }
    679 
    680   public FsFile getAndroidManifestFile() {
    681     return androidManifestFile;
    682   }
    683 
    684   public List<BroadcastReceiverData> getBroadcastReceivers() {
    685     parseAndroidManifest();
    686     return receivers;
    687   }
    688 
    689   public List<ServiceData> getServices() {
    690     parseAndroidManifest();
    691     return new ArrayList<>(serviceDatas.values());
    692   }
    693 
    694   public ServiceData getServiceData(String serviceClassName) {
    695     parseAndroidManifest();
    696     return serviceDatas.get(serviceClassName);
    697   }
    698 
    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
    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
    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 
    756   public ActivityData getActivityData(String activityClassName) {
    757     parseAndroidManifest();
    758     return activityDatas.get(activityClassName);
    759   }
    760 
    761   public String getThemeRef() {
    762     return themeRef;
    763   }
    764 
    765   public Map<String, ActivityData> getActivityDatas() {
    766     parseAndroidManifest();
    767     return activityDatas;
    768   }
    769 
    770   public List<String> getUsedPermissions() {
    771     parseAndroidManifest();
    772     return usedPermissions;
    773   }
    774 
    775   public Map<String, PermissionItemData> getPermissions() {
    776     parseAndroidManifest();
    777     return permissions;
    778   }
    779 
    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    */
    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 
    802   public FsFile getApkFile() {
    803     return apkFile;
    804   }
    805 
    806   /** @deprecated Do not use. */
    807   @Deprecated
    808   public boolean supportsLegacyResourcesMode() {
    809     return true;
    810   }
    811 
    812   /** @deprecated Do not use. */
    813   @Deprecated
    814   synchronized public boolean supportsBinaryResourcesMode() {
    815     if (supportsBinaryResourcesMode == null) {
    816       supportsBinaryResourcesMode = apkFile != null && apkFile.exists();
    817     }
    818     return supportsBinaryResourcesMode;
    819   }
    820 }
    821