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