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 */ 44 public RobolectricConfig(final File baseDir) { 45 this(new File(baseDir, "AndroidManifest.xml"), new File(baseDir, "res"), new File(baseDir, "assets")); 46 } 47 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 */ 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 65 public String getRClassName() throws Exception { 66 parseAndroidManifest(); 67 return rClassName; 68 } 69 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 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 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 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 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 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 180 private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute) { 181 return getTagAttributeIntValue(doc, tag, attribute, null); 182 } 183 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 192 public String getApplicationName() { 193 parseAndroidManifest(); 194 return applicationName; 195 } 196 197 public String getPackageName() { 198 parseAndroidManifest(); 199 return packageName; 200 } 201 202 public int getMinSdkVersion() { 203 parseAndroidManifest(); 204 return minSdkVersion; 205 } 206 207 public int getSdkVersion() { 208 parseAndroidManifest(); 209 return sdkVersion; 210 } 211 212 public int getApplicationFlags() { 213 parseAndroidManifest(); 214 return applicationFlags; 215 } 216 217 public String getProcessName() { 218 parseAndroidManifest(); 219 return processName; 220 } 221 222 public File getResourceDirectory() { 223 return resourceDirectory; 224 } 225 226 public File getAssetsDirectory() { 227 return assetsDirectory; 228 } 229 230 public int getReceiverCount() { 231 parseAndroidManifest(); 232 return receivers.size(); 233 } 234 235 public String getReceiverClassName(final int receiverIndex) { 236 parseAndroidManifest(); 237 return receivers.get(receiverIndex).getBroadcastReceiverClassName(); 238 } 239 240 public List<String> getReceiverIntentFilterActions(final int receiverIndex) { 241 parseAndroidManifest(); 242 return receivers.get(receiverIndex).getIntentFilterActions(); 243 } 244 245 public boolean getStrictI18n() { 246 return strictI18n; 247 } 248 249 public void setStrictI18n(boolean strict) { 250 strictI18n = strict; 251 } 252 253 public void setLocale( String locale ){ 254 this.oldLocale = this.locale; 255 this.locale = locale; 256 } 257 258 public String getLocale() { 259 return this.locale; 260 } 261 262 public boolean isLocaleChanged() { 263 return !locale.equals( oldLocale ); 264 } 265 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 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 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 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 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 337 public ReceiverAndIntentFilter(final String broadcastReceiverClassName, final List<String> intentFilterActions) { 338 this.broadcastReceiverClassName = broadcastReceiverClassName; 339 this.intentFilterActions = intentFilterActions; 340 } 341 342 public String getBroadcastReceiverClassName() { 343 return broadcastReceiverClassName; 344 } 345 346 public List<String> getIntentFilterActions() { 347 return intentFilterActions; 348 } 349 } 350 } 351