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