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