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