1 package com.xtremelabs.robolectric;
2 
3 import android.app.Application;
4 import com.xtremelabs.robolectric.internal.ClassNameResolver;
5 import org.w3c.dom.Document;
6 import org.w3c.dom.Node;
7 import org.w3c.dom.NodeList;
8 
9 import javax.xml.parsers.DocumentBuilder;
10 import javax.xml.parsers.DocumentBuilderFactory;
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.util.ArrayList;
14 import java.util.List;
15 
16 import static android.content.pm.ApplicationInfo.*;
17 
18 public class RobolectricConfig {
19     private final File androidManifestFile;
20     private final File resourceDirectory;
21     private final File assetsDirectory;
22     private String rClassName;
23     private String packageName;
24     private String processName;
25     private String applicationName;
26     private boolean manifestIsParsed = false;
27     private int sdkVersion;
28     private int minSdkVersion;
29     private boolean sdkVersionSpecified = true;
30     private boolean minSdkVersionSpecified = true;
31     private int applicationFlags;
32     private final List<ReceiverAndIntentFilter> receivers = new ArrayList<ReceiverAndIntentFilter>();
33     private boolean strictI18n = false;
34     private String locale = "";
35     private String oldLocale = "";
36 
37     /**
38      * Creates a Robolectric configuration using default Android files relative to the specified base directory.
39      * <p/>
40      * The manifest will be baseDir/AndroidManifest.xml, res will be baseDir/res, and assets in baseDir/assets.
41      *
42      * @param baseDir the base directory of your Android project
43      */
RobolectricConfig(final File baseDir)44     public RobolectricConfig(final File baseDir) {
45         this(new File(baseDir, "AndroidManifest.xml"), new File(baseDir, "res"), new File(baseDir, "assets"));
46     }
47 
RobolectricConfig(final File androidManifestFile, final File resourceDirectory)48     public RobolectricConfig(final File androidManifestFile, final File resourceDirectory) {
49         this(androidManifestFile, resourceDirectory, new File(resourceDirectory.getParent(), "assets"));
50     }
51 
52     /**
53      * Creates a Robolectric configuration using specified locations.
54      *
55      * @param androidManifestFile location of the AndroidManifest.xml file
56      * @param resourceDirectory   location of the res directory
57      * @param assetsDirectory     location of the assets directory
58      */
RobolectricConfig(final File androidManifestFile, final File resourceDirectory, final File assetsDirectory)59     public RobolectricConfig(final File androidManifestFile, final File resourceDirectory, final File assetsDirectory) {
60         this.androidManifestFile = androidManifestFile;
61         this.resourceDirectory = resourceDirectory;
62         this.assetsDirectory = assetsDirectory;
63     }
64 
getRClassName()65     public String getRClassName() throws Exception {
66         parseAndroidManifest();
67         return rClassName;
68     }
69 
validate()70     public void validate() throws FileNotFoundException {
71         if (!androidManifestFile.exists() || !androidManifestFile.isFile()) {
72             throw new FileNotFoundException(androidManifestFile.getAbsolutePath() + " not found or not a file; it should point to your project's AndroidManifest.xml");
73         }
74 
75         if (!getResourceDirectory().exists() || !getResourceDirectory().isDirectory()) {
76             throw new FileNotFoundException(getResourceDirectory().getAbsolutePath() + " not found or not a directory; it should point to your project's res directory");
77         }
78     }
79 
parseAndroidManifest()80     private void parseAndroidManifest() {
81         if (manifestIsParsed) {
82             return;
83         }
84         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
85         try {
86             DocumentBuilder db = dbf.newDocumentBuilder();
87             Document manifestDocument = db.parse(androidManifestFile);
88 
89             packageName = getTagAttributeText(manifestDocument, "manifest", "package");
90             rClassName = packageName + ".R";
91             applicationName = getTagAttributeText(manifestDocument, "application", "android:name");
92             Integer minSdkVer = getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:minSdkVersion");
93             Integer sdkVer = getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:targetSdkVersion");
94             if (minSdkVer == null) {
95                 minSdkVersion = 10;
96                 minSdkVersionSpecified = false;
97             } else {
98                 minSdkVersion = minSdkVer;
99             }
100             if (sdkVer == null) {
101                 sdkVersion = 10;
102                 sdkVersionSpecified = false;
103             } else {
104                 sdkVersion = sdkVer;
105             }
106 
107             processName = getTagAttributeText(manifestDocument, "application", "android:process");
108             if (processName == null) {
109             	processName = packageName;
110             }
111 
112             parseApplicationFlags(manifestDocument);
113             parseReceivers(manifestDocument, packageName);
114         } catch (Exception ignored) {
115         }
116         manifestIsParsed = true;
117     }
118 
parseReceivers(final Document manifestDocument, String packageName)119     private void parseReceivers(final Document manifestDocument, String packageName) {
120         Node application = manifestDocument.getElementsByTagName("application").item(0);
121         if (application == null) {
122             return;
123         }
124         for (Node receiverNode : getChildrenTags(application, "receiver")) {
125             Node namedItem = receiverNode.getAttributes().getNamedItem("android:name");
126             if (namedItem == null) {
127                 continue;
128             }
129             String receiverName = namedItem.getTextContent();
130             if (receiverName.startsWith(".")) {
131                 receiverName = packageName + receiverName;
132             }
133             for (Node intentFilterNode : getChildrenTags(receiverNode, "intent-filter")) {
134                 List<String> actions = new ArrayList<String>();
135                 for (Node actionNode : getChildrenTags(intentFilterNode, "action")) {
136                     Node nameNode = actionNode.getAttributes().getNamedItem("android:name");
137                     if (nameNode != null) {
138                         actions.add(nameNode.getTextContent());
139                     }
140                 }
141                 receivers.add(new ReceiverAndIntentFilter(receiverName, actions));
142             }
143         }
144     }
145 
getChildrenTags(final Node node, final String tagName)146     private List<Node> getChildrenTags(final Node node, final String tagName) {
147         List<Node> children = new ArrayList<Node>();
148         for (int i = 0; i < node.getChildNodes().getLength(); i++) {
149             Node childNode = node.getChildNodes().item(i);
150             if (childNode.getNodeName().equalsIgnoreCase(tagName)) {
151                 children.add(childNode);
152             }
153         }
154         return children;
155     }
156 
parseApplicationFlags(final Document manifestDocument)157     private void parseApplicationFlags(final Document manifestDocument) {
158         applicationFlags = getApplicationFlag(manifestDocument, "android:allowBackup", FLAG_ALLOW_BACKUP);
159         applicationFlags += getApplicationFlag(manifestDocument, "android:allowClearUserData", FLAG_ALLOW_CLEAR_USER_DATA);
160         applicationFlags += getApplicationFlag(manifestDocument, "android:allowTaskReparenting", FLAG_ALLOW_TASK_REPARENTING);
161         applicationFlags += getApplicationFlag(manifestDocument, "android:debuggable", FLAG_DEBUGGABLE);
162         applicationFlags += getApplicationFlag(manifestDocument, "android:hasCode", FLAG_HAS_CODE);
163         applicationFlags += getApplicationFlag(manifestDocument, "android:killAfterRestore", FLAG_KILL_AFTER_RESTORE);
164         applicationFlags += getApplicationFlag(manifestDocument, "android:persistent", FLAG_PERSISTENT);
165         applicationFlags += getApplicationFlag(manifestDocument, "android:resizeable", FLAG_RESIZEABLE_FOR_SCREENS);
166         applicationFlags += getApplicationFlag(manifestDocument, "android:restoreAnyVersion", FLAG_RESTORE_ANY_VERSION);
167         applicationFlags += getApplicationFlag(manifestDocument, "android:largeScreens", FLAG_SUPPORTS_LARGE_SCREENS);
168         applicationFlags += getApplicationFlag(manifestDocument, "android:normalScreens", FLAG_SUPPORTS_NORMAL_SCREENS);
169         applicationFlags += getApplicationFlag(manifestDocument, "android:anyDensity", FLAG_SUPPORTS_SCREEN_DENSITIES);
170         applicationFlags += getApplicationFlag(manifestDocument, "android:smallScreens", FLAG_SUPPORTS_SMALL_SCREENS);
171         applicationFlags += getApplicationFlag(manifestDocument, "android:testOnly", FLAG_TEST_ONLY);
172         applicationFlags += getApplicationFlag(manifestDocument, "android:vmSafeMode", FLAG_VM_SAFE_MODE);
173     }
174 
getApplicationFlag(final Document doc, final String attribute, final int attributeValue)175     private int getApplicationFlag(final Document doc, final String attribute, final int attributeValue) {
176     	String flagString = getTagAttributeText(doc, "application", attribute);
177     	return "true".equalsIgnoreCase(flagString) ? attributeValue : 0;
178     }
179 
getTagAttributeIntValue(final Document doc, final String tag, final String attribute)180     private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute) {
181         return getTagAttributeIntValue(doc, tag, attribute, null);
182     }
183 
getTagAttributeIntValue(final Document doc, final String tag, final String attribute, final Integer defaultValue)184     private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute, final Integer defaultValue) {
185         String valueString = getTagAttributeText(doc, tag, attribute);
186         if (valueString != null) {
187             return Integer.parseInt(valueString);
188         }
189         return defaultValue;
190     }
191 
getApplicationName()192     public String getApplicationName() {
193         parseAndroidManifest();
194         return applicationName;
195     }
196 
getPackageName()197     public String getPackageName() {
198         parseAndroidManifest();
199         return packageName;
200     }
201 
getMinSdkVersion()202     public int getMinSdkVersion() {
203     	parseAndroidManifest();
204 		return minSdkVersion;
205 	}
206 
getSdkVersion()207     public int getSdkVersion() {
208         parseAndroidManifest();
209         return sdkVersion;
210     }
211 
getApplicationFlags()212     public int getApplicationFlags() {
213     	parseAndroidManifest();
214     	return applicationFlags;
215     }
216 
getProcessName()217     public String getProcessName() {
218 		parseAndroidManifest();
219 		return processName;
220 	}
221 
getResourceDirectory()222     public File getResourceDirectory() {
223         return resourceDirectory;
224     }
225 
getAssetsDirectory()226     public File getAssetsDirectory() {
227         return assetsDirectory;
228     }
229 
getReceiverCount()230     public int getReceiverCount() {
231         parseAndroidManifest();
232         return receivers.size();
233     }
234 
getReceiverClassName(final int receiverIndex)235     public String getReceiverClassName(final int receiverIndex) {
236         parseAndroidManifest();
237         return receivers.get(receiverIndex).getBroadcastReceiverClassName();
238     }
239 
getReceiverIntentFilterActions(final int receiverIndex)240     public List<String> getReceiverIntentFilterActions(final int receiverIndex) {
241         parseAndroidManifest();
242         return receivers.get(receiverIndex).getIntentFilterActions();
243     }
244 
getStrictI18n()245     public boolean getStrictI18n() {
246     	return strictI18n;
247     }
248 
setStrictI18n(boolean strict)249     public void setStrictI18n(boolean strict) {
250     	strictI18n = strict;
251     }
252 
setLocale( String locale )253     public void setLocale( String locale ){
254     	this.oldLocale = this.locale;
255     	this.locale = locale;
256     }
257 
getLocale()258     public String getLocale() {
259     	return this.locale;
260     }
261 
isLocaleChanged()262     public boolean isLocaleChanged() {
263     	return !locale.equals( oldLocale );
264     }
265 
getTagAttributeText(final Document doc, final String tag, final String attribute)266     private static String getTagAttributeText(final Document doc, final String tag, final String attribute) {
267         NodeList elementsByTagName = doc.getElementsByTagName(tag);
268         for (int i = 0; i < elementsByTagName.getLength(); ++i) {
269             Node item = elementsByTagName.item(i);
270             Node namedItem = item.getAttributes().getNamedItem(attribute);
271             if (namedItem != null) {
272                 return namedItem.getTextContent();
273             }
274         }
275         return null;
276     }
277 
newApplicationInstance(final String packageName, final String applicationName)278     private static Application newApplicationInstance(final String packageName, final String applicationName) {
279         Application application;
280         try {
281             Class<? extends Application> applicationClass =
282                     new ClassNameResolver<Application>(packageName, applicationName).resolve();
283             application = applicationClass.newInstance();
284         } catch (Exception e) {
285             throw new RuntimeException(e);
286         }
287         return application;
288     }
289 
290     @Override
equals(final Object o)291     public boolean equals(final Object o) {
292         if (this == o) {
293             return true;
294         }
295         if (o == null || getClass() != o.getClass()) {
296             return false;
297         }
298 
299         RobolectricConfig that = (RobolectricConfig) o;
300 
301         if (androidManifestFile != null ? !androidManifestFile.equals(that.androidManifestFile) : that.androidManifestFile != null) {
302             return false;
303         }
304         if (getAssetsDirectory() != null ? !getAssetsDirectory().equals(that.getAssetsDirectory()) : that.getAssetsDirectory() != null) {
305             return false;
306         }
307         if (getResourceDirectory() != null ? !getResourceDirectory().equals(that.getResourceDirectory()) : that.getResourceDirectory() != null) {
308             return false;
309         }
310 
311         return true;
312     }
313 
314     @Override
hashCode()315     public int hashCode() {
316         int result = androidManifestFile != null ? androidManifestFile.hashCode() : 0;
317         result = 31 * result + (getResourceDirectory() != null ? getResourceDirectory().hashCode() : 0);
318         result = 31 * result + (getAssetsDirectory() != null ? getAssetsDirectory().hashCode() : 0);
319         return result;
320     }
321 
getRealSdkVersion()322     public int getRealSdkVersion() {
323         parseAndroidManifest();
324         if (sdkVersionSpecified) {
325             return sdkVersion;
326         }
327         if (minSdkVersionSpecified) {
328             return minSdkVersion;
329         }
330         return sdkVersion;
331     }
332 
333     private static class ReceiverAndIntentFilter {
334         private final List<String> intentFilterActions;
335         private final String broadcastReceiverClassName;
336 
ReceiverAndIntentFilter(final String broadcastReceiverClassName, final List<String> intentFilterActions)337         public ReceiverAndIntentFilter(final String broadcastReceiverClassName, final List<String> intentFilterActions) {
338             this.broadcastReceiverClassName = broadcastReceiverClassName;
339             this.intentFilterActions = intentFilterActions;
340         }
341 
getBroadcastReceiverClassName()342         public String getBroadcastReceiverClassName() {
343             return broadcastReceiverClassName;
344         }
345 
getIntentFilterActions()346         public List<String> getIntentFilterActions() {
347             return intentFilterActions;
348         }
349     }
350 }
351