Adding attributes and intent filters to BroadcastReceiverData and ServiceData as parsed from the Android manifest.
diff --git a/resources/src/main/java/org/robolectric/manifest/ActivityData.java b/resources/src/main/java/org/robolectric/manifest/ActivityData.java
index 7088115..0da5d19 100644
--- a/resources/src/main/java/org/robolectric/manifest/ActivityData.java
+++ b/resources/src/main/java/org/robolectric/manifest/ActivityData.java
@@ -15,7 +15,6 @@
private static final String EXPORTED = "exported";
private static final String FINISH_ON_TASK_LAUNCH = "finishOnTaskLaunch";
private static final String HARDWARE_ACCELERATED = "hardwareAccelerated";
- private static final String ICON = "icon";
private static final String LABEL = "label";
private static final String LAUNCH_MODE = "launchMode";
private static final String MULTIPROCESS = "multiprocess";
@@ -86,7 +85,7 @@
}
public boolean isExported() {
- boolean defaultValue = false;
+ boolean defaultValue = !intentFilters.isEmpty();
return getBooleanAttr(withXMLNS(EXPORTED), defaultValue);
}
diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
index e4f6b41..7fb1054 100644
--- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
+++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
@@ -243,15 +243,32 @@
return attributeNode == null ? null : attributeNode.getTextContent();
}
+ private static HashMap<String, String> parseNodeAttributes(Node node) {
+ final NamedNodeMap attributes = node.getAttributes();
+ final int attrCount = attributes.getLength();
+ final HashMap<String, String> receiverAttrs = new HashMap<>(attributes.getLength());
+ for (int i = 0; i < attrCount; i++) {
+ Node attribute = attributes.item(i);
+ String value = attribute.getNodeValue();
+ if (value != null) {
+ receiverAttrs.put(attribute.getNodeName(), value);
+ }
+ }
+ return receiverAttrs;
+ }
+
private void parseReceivers(Node applicationNode) {
for (Node receiverNode : getChildrenTags(applicationNode, "receiver")) {
- Node namedItem = receiverNode.getAttributes().getNamedItem("android:name");
- if (namedItem == null) continue;
+ final HashMap<String, String> receiverAttrs = parseNodeAttributes(receiverNode);
- String receiverName = resolveClassRef(namedItem.getTextContent());
+ String receiverName = resolveClassRef(receiverAttrs.get("android:name"));
+ receiverAttrs.put("android:name", receiverName);
+
MetaData metaData = new MetaData(getChildrenTags(receiverNode, "meta-data"));
- BroadcastReceiverData receiver = new BroadcastReceiverData(receiverName, metaData);
+ final List<IntentFilterData> intentFilterData = parseIntentFilters(receiverNode);
+ BroadcastReceiverData receiver =
+ new BroadcastReceiverData(receiverAttrs, metaData, intentFilterData);
List<Node> intentFilters = getChildrenTags(receiverNode, "intent-filter");
for (Node intentFilterNode : intentFilters) {
for (Node actionNode : getChildrenTags(intentFilterNode, "action")) {
@@ -261,26 +278,22 @@
}
}
}
-
- Node permissionItem = receiverNode.getAttributes().getNamedItem("android:permission");
- if (permissionItem != null) {
- receiver.setPermission(permissionItem.getTextContent());
- }
-
+
receivers.add(receiver);
}
}
private void parseServices(Node applicationNode) {
for (Node serviceNode : getChildrenTags(applicationNode, "service")) {
- Node namedItem = serviceNode.getAttributes().getNamedItem("android:name");
- if (namedItem == null) continue;
+ final HashMap<String, String> serviceAttrs = parseNodeAttributes(serviceNode);
- String serviceName = resolveClassRef(namedItem.getTextContent());
+ String serviceName = resolveClassRef(serviceAttrs.get("android:name"));
+ serviceAttrs.put("android:name", serviceName);
+
MetaData metaData = new MetaData(getChildrenTags(serviceNode, "meta-data"));
final List<IntentFilterData> intentFilterData = parseIntentFilters(serviceNode);
- ServiceData service = new ServiceData(serviceName, metaData, intentFilterData);
+ ServiceData service = new ServiceData(serviceAttrs, metaData, intentFilterData);
List<Node> intentFilters = getChildrenTags(serviceNode, "intent-filter");
for (Node intentFilterNode : intentFilters) {
for (Node actionNode : getChildrenTags(intentFilterNode, "action")) {
@@ -291,10 +304,6 @@
}
}
- Node permissionItem = serviceNode.getAttributes().getNamedItem("android:permission");
- if (permissionItem != null) {
- service.setPermission(permissionItem.getTextContent());
- }
serviceDatas.put(serviceName, service);
}
}
@@ -318,18 +327,9 @@
}
private void parseActivity(Node activityNode, boolean isAlias) {
- final NamedNodeMap attributes = activityNode.getAttributes();
- final int attrCount = attributes.getLength();
final List<IntentFilterData> intentFilterData = parseIntentFilters(activityNode);
final MetaData metaData = new MetaData(getChildrenTags(activityNode, "meta-data"));
- final HashMap<String, String> activityAttrs = new HashMap<>(attrCount);
- for(int i = 0; i < attrCount; i++) {
- Node attr = attributes.item(i);
- String v = attr.getNodeValue();
- if( v != null) {
- activityAttrs.put(attr.getNodeName(), v);
- }
- }
+ final HashMap<String, String> activityAttrs = parseNodeAttributes(activityNode);
String activityName = resolveClassRef(activityAttrs.get(ActivityData.getNameAttr("android")));
if (activityName == null) {
@@ -596,6 +596,7 @@
}
public ServiceData getServiceData(String serviceClassName) {
+ parseAndroidManifest();
return serviceDatas.get(serviceClassName);
}
@@ -659,4 +660,21 @@
parseAndroidManifest();
return permissions;
}
+
+ /**
+ * Returns data for the broadcast receiver with the provided name from this manifest. If no
+ * receiver with the class name can be found, returns null.
+ *
+ * @param className the fully resolved class name of the receiver
+ * @return data for the receiver or null if it cannot be found
+ */
+ public @Nullable BroadcastReceiverData getBroadcastReceiver(String className) {
+ parseAndroidManifest();
+ for (BroadcastReceiverData receiver : receivers) {
+ if (receiver.getClassName().equals(className)) {
+ return receiver;
+ }
+ }
+ return null;
+ }
}
diff --git a/resources/src/main/java/org/robolectric/manifest/BroadcastReceiverData.java b/resources/src/main/java/org/robolectric/manifest/BroadcastReceiverData.java
index 9d549de..37b0863 100644
--- a/resources/src/main/java/org/robolectric/manifest/BroadcastReceiverData.java
+++ b/resources/src/main/java/org/robolectric/manifest/BroadcastReceiverData.java
@@ -1,15 +1,35 @@
package org.robolectric.manifest;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
public class BroadcastReceiverData extends PackageItemData {
+
+ private static final String EXPORTED = "android:exported";
+ private static final String NAME = "android:name";
+ private static final String PERMISSION = "android:permission";
+
+ private final Map<String, String> attributes;
private final List<String> actions;
- private String permission;
+ private List<IntentFilterData> intentFilters;
+
+ public BroadcastReceiverData(
+ Map<String, String> attributes, MetaData metaData, List<IntentFilterData> intentFilters) {
+ super(attributes.get(NAME), metaData);
+ this.attributes = attributes;
+ this.actions = new ArrayList<>();
+ this.intentFilters = new LinkedList<>(intentFilters);
+ }
public BroadcastReceiverData(String className, MetaData metaData) {
super(className, metaData);
this.actions = new ArrayList<>();
+ this.attributes = new HashMap<>();
+ intentFilters = new LinkedList<>();
}
public List<String> getActions() {
@@ -21,10 +41,42 @@
}
public void setPermission(final String permission) {
- this.permission = permission;
+ attributes.put(PERMISSION, permission);
}
public String getPermission() {
- return permission;
+ return attributes.get(PERMISSION);
+ }
+
+ /**
+ * Get the intent filters defined for the broadcast receiver.
+ *
+ * @return A list of intent filters.
+ */
+ @Nonnull
+ public List<IntentFilterData> getIntentFilters() {
+ return intentFilters;
+ }
+
+ /**
+ * Get the map for all attributes defined for the broadcast receiver.
+ *
+ * @return map of attributes names to values from the manifest.
+ */
+ @Nonnull
+ public Map<String, String> getAllAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Returns whether this broadcast receiver is exported by checking the XML attribute.
+ *
+ * @return true if the broadcast receiver is exported
+ */
+ public boolean isExported() {
+ boolean defaultValue = !intentFilters.isEmpty();
+ return (attributes.containsKey(EXPORTED)
+ ? Boolean.parseBoolean(attributes.get(EXPORTED))
+ : defaultValue);
}
}
diff --git a/resources/src/main/java/org/robolectric/manifest/ServiceData.java b/resources/src/main/java/org/robolectric/manifest/ServiceData.java
index cde5bcc..b763595 100644
--- a/resources/src/main/java/org/robolectric/manifest/ServiceData.java
+++ b/resources/src/main/java/org/robolectric/manifest/ServiceData.java
@@ -1,25 +1,41 @@
package org.robolectric.manifest;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
public class ServiceData {
- private final String className;
+
+ private static final String EXPORTED = "android:exported";
+ private static final String NAME = "android:name";
+ private static final String PERMISSION = "android:permission";
+
+ private final Map<String, String> attributes;
private final MetaData metaData;
private final List<String> actions;
- private String permission;
private List<IntentFilterData> intentFilters;
- public ServiceData(String className, MetaData metaData, List<IntentFilterData> intentFilterData) {
+ public ServiceData(
+ Map<String, String> attributes, MetaData metaData, List<IntentFilterData> intentFilters) {
+ this.attributes = attributes;
this.actions = new ArrayList<>();
- this.className = className;
+ this.metaData = metaData;
+ this.intentFilters = new LinkedList<>(intentFilters);
+ }
+
+ public ServiceData(String className, MetaData metaData, List<IntentFilterData> intentFilterData) {
+ this.attributes = new HashMap<>();
+ this.attributes.put(NAME, className);
+ this.actions = new ArrayList<>();
this.metaData = metaData;
intentFilters = new LinkedList<>(intentFilterData);
}
public String getClassName() {
- return className;
+ return attributes.get(NAME);
}
public List<String> getActions() {
@@ -35,18 +51,42 @@
}
public void setPermission(final String permission) {
- this.permission = permission;
+ attributes.put(PERMISSION, permission);
}
public String getPermission() {
- return permission;
+ return attributes.get(PERMISSION);
}
/**
- * Get the intent filters defined for activity.
- * @return A list of intent filters. Not null.
+ * Get the intent filters defined for the service.
+ *
+ * @return A list of intent filters.
*/
+ @Nonnull
public List<IntentFilterData> getIntentFilters() {
return intentFilters;
}
+
+ /**
+ * Get the map for all attributes defined for the service.
+ *
+ * @return map of attributes names to values from the manifest.
+ */
+ @Nonnull
+ public Map<String, String> getAllAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Returns whether this service is exported by checking the XML attribute.
+ *
+ * @return true if the service is exported
+ */
+ public boolean isExported() {
+ boolean defaultValue = !intentFilters.isEmpty();
+ return (attributes.containsKey(EXPORTED)
+ ? Boolean.parseBoolean(attributes.get(EXPORTED))
+ : defaultValue);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
index b95f867..5536648 100644
--- a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
+++ b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
@@ -76,6 +76,9 @@
assertThat(config.getBroadcastReceivers().get(5).getClassName()).isEqualTo("com.foo.Receiver");
assertThat(config.getBroadcastReceivers().get(5).getActions()).contains("org.robolectric.ACTION_DIFFERENT_PACKAGE");
+ assertThat(config.getBroadcastReceivers().get(5).getIntentFilters()).hasSize(1);
+ IntentFilterData filter = config.getBroadcastReceivers().get(5).getIntentFilters().get(0);
+ assertThat(filter.getActions()).containsExactly("org.robolectric.ACTION_DIFFERENT_PACKAGE");
assertThat(config.getBroadcastReceivers().get(6).getClassName()).isEqualTo("com.bar.ReceiverWithoutIntentFilter");
assertThat(config.getBroadcastReceivers().get(6).getActions()).isEmpty();
@@ -100,7 +103,7 @@
assertThat(config.getServiceData("com.foo.Service").getClassName()).isEqualTo("com.foo.Service");
assertThat(config.getServiceData("com.bar.ServiceWithoutIntentFilter").getClassName()).isEqualTo("com.bar.ServiceWithoutIntentFilter");
- assertEquals(config.getServiceData("com.foo.Service").getPermission(), "com.foo.Permission");
+ assertThat(config.getServiceData("com.foo.Service").getPermission()).isEqualTo("com.foo.Permission");
}
@Test
@@ -248,7 +251,7 @@
@Test
public void shouldTolerateMissingRFile() throws Exception {
AndroidManifest appManifest = new AndroidManifest(resourceFile("TestAndroidManifestWithNoRFile.xml"), resourceFile("res"), resourceFile("assets"));
- assertEquals(appManifest.getPackageName(), "org.no.resources.for.me");
+ assertThat(appManifest.getPackageName()).isEqualTo("org.no.resources.for.me");
assertThat(appManifest.getRClass()).isNull();
}
@@ -424,6 +427,51 @@
assertThat(wrongFields).isEmpty();
}
+ @Test
+ public void activitiesWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestForActivities.xml");
+ ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity");
+ assertThat(activityData.isExported()).isFalse();
+ }
+
+ @Test
+ public void activitiesWithIntentFiltersExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestForActivitiesWithIntentFilter.xml");
+ ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity");
+ assertThat(activityData.isExported()).isTrue();
+ }
+
+ @Test
+ public void servicesWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml");
+ ServiceData serviceData = config.getServiceData("com.bar.ServiceWithoutIntentFilter");
+ assertThat(serviceData.isExported()).isFalse();
+ }
+
+ @Test
+ public void servicesWithIntentFiltersExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml");
+ ServiceData serviceData = config.getServiceData("com.foo.Service");
+ assertThat(serviceData.isExported()).isTrue();
+ }
+
+ @Test
+ public void receiversWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
+ BroadcastReceiverData receiverData =
+ config.getBroadcastReceiver("com.bar.ReceiverWithoutIntentFilter");
+ assertThat(receiverData).isNotNull();
+ assertThat(receiverData.isExported()).isFalse();
+ }
+
+ @Test
+ public void receiversWithIntentFiltersExportedByDefault() throws Exception {
+ AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
+ BroadcastReceiverData receiverData = config.getBroadcastReceiver("com.foo.Receiver");
+ assertThat(receiverData).isNotNull();
+ assertThat(receiverData.isExported()).isTrue();
+ }
+
/////////////////////////////
private AndroidManifest newConfigWith(String fileName, String usesSdkAttrs) throws IOException {