package com.xtremelabs.robolectric; import android.app.Application; import com.xtremelabs.robolectric.internal.ClassNameResolver; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import static android.content.pm.ApplicationInfo.*; public class RobolectricConfig { private final File androidManifestFile; private final File resourceDirectory; private final File assetsDirectory; private String rClassName; private String packageName; private String processName; private String applicationName; private boolean manifestIsParsed = false; private int sdkVersion; private int minSdkVersion; private boolean sdkVersionSpecified = true; private boolean minSdkVersionSpecified = true; private int applicationFlags; private final List receivers = new ArrayList(); private boolean strictI18n = false; private String locale = ""; private String oldLocale = ""; /** * Creates a Robolectric configuration using default Android files relative to the specified base directory. *

* The manifest will be baseDir/AndroidManifest.xml, res will be baseDir/res, and assets in baseDir/assets. * * @param baseDir the base directory of your Android project */ public RobolectricConfig(final File baseDir) { this(new File(baseDir, "AndroidManifest.xml"), new File(baseDir, "res"), new File(baseDir, "assets")); } public RobolectricConfig(final File androidManifestFile, final File resourceDirectory) { this(androidManifestFile, resourceDirectory, new File(resourceDirectory.getParent(), "assets")); } /** * Creates a Robolectric configuration using specified locations. * * @param androidManifestFile location of the AndroidManifest.xml file * @param resourceDirectory location of the res directory * @param assetsDirectory location of the assets directory */ public RobolectricConfig(final File androidManifestFile, final File resourceDirectory, final File assetsDirectory) { this.androidManifestFile = androidManifestFile; this.resourceDirectory = resourceDirectory; this.assetsDirectory = assetsDirectory; } public String getRClassName() throws Exception { parseAndroidManifest(); return rClassName; } public void validate() throws FileNotFoundException { if (!androidManifestFile.exists() || !androidManifestFile.isFile()) { throw new FileNotFoundException(androidManifestFile.getAbsolutePath() + " not found or not a file; it should point to your project's AndroidManifest.xml"); } if (!getResourceDirectory().exists() || !getResourceDirectory().isDirectory()) { throw new FileNotFoundException(getResourceDirectory().getAbsolutePath() + " not found or not a directory; it should point to your project's res directory"); } } private void parseAndroidManifest() { if (manifestIsParsed) { return; } DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document manifestDocument = db.parse(androidManifestFile); packageName = getTagAttributeText(manifestDocument, "manifest", "package"); rClassName = packageName + ".R"; applicationName = getTagAttributeText(manifestDocument, "application", "android:name"); Integer minSdkVer = getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:minSdkVersion"); Integer sdkVer = getTagAttributeIntValue(manifestDocument, "uses-sdk", "android:targetSdkVersion"); if (minSdkVer == null) { minSdkVersion = 10; minSdkVersionSpecified = false; } else { minSdkVersion = minSdkVer; } if (sdkVer == null) { sdkVersion = 10; sdkVersionSpecified = false; } else { sdkVersion = sdkVer; } processName = getTagAttributeText(manifestDocument, "application", "android:process"); if (processName == null) { processName = packageName; } parseApplicationFlags(manifestDocument); parseReceivers(manifestDocument, packageName); } catch (Exception ignored) { } manifestIsParsed = true; } private void parseReceivers(final Document manifestDocument, String packageName) { Node application = manifestDocument.getElementsByTagName("application").item(0); if (application == null) { return; } for (Node receiverNode : getChildrenTags(application, "receiver")) { Node namedItem = receiverNode.getAttributes().getNamedItem("android:name"); if (namedItem == null) { continue; } String receiverName = namedItem.getTextContent(); if (receiverName.startsWith(".")) { receiverName = packageName + receiverName; } for (Node intentFilterNode : getChildrenTags(receiverNode, "intent-filter")) { List actions = new ArrayList(); for (Node actionNode : getChildrenTags(intentFilterNode, "action")) { Node nameNode = actionNode.getAttributes().getNamedItem("android:name"); if (nameNode != null) { actions.add(nameNode.getTextContent()); } } receivers.add(new ReceiverAndIntentFilter(receiverName, actions)); } } } private List getChildrenTags(final Node node, final String tagName) { List children = new ArrayList(); for (int i = 0; i < node.getChildNodes().getLength(); i++) { Node childNode = node.getChildNodes().item(i); if (childNode.getNodeName().equalsIgnoreCase(tagName)) { children.add(childNode); } } return children; } private void parseApplicationFlags(final Document manifestDocument) { applicationFlags = getApplicationFlag(manifestDocument, "android:allowBackup", FLAG_ALLOW_BACKUP); applicationFlags += getApplicationFlag(manifestDocument, "android:allowClearUserData", FLAG_ALLOW_CLEAR_USER_DATA); applicationFlags += getApplicationFlag(manifestDocument, "android:allowTaskReparenting", FLAG_ALLOW_TASK_REPARENTING); applicationFlags += getApplicationFlag(manifestDocument, "android:debuggable", FLAG_DEBUGGABLE); applicationFlags += getApplicationFlag(manifestDocument, "android:hasCode", FLAG_HAS_CODE); applicationFlags += getApplicationFlag(manifestDocument, "android:killAfterRestore", FLAG_KILL_AFTER_RESTORE); applicationFlags += getApplicationFlag(manifestDocument, "android:persistent", FLAG_PERSISTENT); applicationFlags += getApplicationFlag(manifestDocument, "android:resizeable", FLAG_RESIZEABLE_FOR_SCREENS); applicationFlags += getApplicationFlag(manifestDocument, "android:restoreAnyVersion", FLAG_RESTORE_ANY_VERSION); applicationFlags += getApplicationFlag(manifestDocument, "android:largeScreens", FLAG_SUPPORTS_LARGE_SCREENS); applicationFlags += getApplicationFlag(manifestDocument, "android:normalScreens", FLAG_SUPPORTS_NORMAL_SCREENS); applicationFlags += getApplicationFlag(manifestDocument, "android:anyDensity", FLAG_SUPPORTS_SCREEN_DENSITIES); applicationFlags += getApplicationFlag(manifestDocument, "android:smallScreens", FLAG_SUPPORTS_SMALL_SCREENS); applicationFlags += getApplicationFlag(manifestDocument, "android:testOnly", FLAG_TEST_ONLY); applicationFlags += getApplicationFlag(manifestDocument, "android:vmSafeMode", FLAG_VM_SAFE_MODE); } private int getApplicationFlag(final Document doc, final String attribute, final int attributeValue) { String flagString = getTagAttributeText(doc, "application", attribute); return "true".equalsIgnoreCase(flagString) ? attributeValue : 0; } private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute) { return getTagAttributeIntValue(doc, tag, attribute, null); } private Integer getTagAttributeIntValue(final Document doc, final String tag, final String attribute, final Integer defaultValue) { String valueString = getTagAttributeText(doc, tag, attribute); if (valueString != null) { return Integer.parseInt(valueString); } return defaultValue; } public String getApplicationName() { parseAndroidManifest(); return applicationName; } public String getPackageName() { parseAndroidManifest(); return packageName; } public int getMinSdkVersion() { parseAndroidManifest(); return minSdkVersion; } public int getSdkVersion() { parseAndroidManifest(); return sdkVersion; } public int getApplicationFlags() { parseAndroidManifest(); return applicationFlags; } public String getProcessName() { parseAndroidManifest(); return processName; } public File getResourceDirectory() { return resourceDirectory; } public File getAssetsDirectory() { return assetsDirectory; } public int getReceiverCount() { parseAndroidManifest(); return receivers.size(); } public String getReceiverClassName(final int receiverIndex) { parseAndroidManifest(); return receivers.get(receiverIndex).getBroadcastReceiverClassName(); } public List getReceiverIntentFilterActions(final int receiverIndex) { parseAndroidManifest(); return receivers.get(receiverIndex).getIntentFilterActions(); } public boolean getStrictI18n() { return strictI18n; } public void setStrictI18n(boolean strict) { strictI18n = strict; } public void setLocale( String locale ){ this.oldLocale = this.locale; this.locale = locale; } public String getLocale() { return this.locale; } public boolean isLocaleChanged() { return !locale.equals( oldLocale ); } private static String getTagAttributeText(final Document doc, final String tag, final String attribute) { NodeList elementsByTagName = doc.getElementsByTagName(tag); for (int i = 0; i < elementsByTagName.getLength(); ++i) { Node item = elementsByTagName.item(i); Node namedItem = item.getAttributes().getNamedItem(attribute); if (namedItem != null) { return namedItem.getTextContent(); } } return null; } private static Application newApplicationInstance(final String packageName, final String applicationName) { Application application; try { Class applicationClass = new ClassNameResolver(packageName, applicationName).resolve(); application = applicationClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return application; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } RobolectricConfig that = (RobolectricConfig) o; if (androidManifestFile != null ? !androidManifestFile.equals(that.androidManifestFile) : that.androidManifestFile != null) { return false; } if (getAssetsDirectory() != null ? !getAssetsDirectory().equals(that.getAssetsDirectory()) : that.getAssetsDirectory() != null) { return false; } if (getResourceDirectory() != null ? !getResourceDirectory().equals(that.getResourceDirectory()) : that.getResourceDirectory() != null) { return false; } return true; } @Override public int hashCode() { int result = androidManifestFile != null ? androidManifestFile.hashCode() : 0; result = 31 * result + (getResourceDirectory() != null ? getResourceDirectory().hashCode() : 0); result = 31 * result + (getAssetsDirectory() != null ? getAssetsDirectory().hashCode() : 0); return result; } public int getRealSdkVersion() { parseAndroidManifest(); if (sdkVersionSpecified) { return sdkVersion; } if (minSdkVersionSpecified) { return minSdkVersion; } return sdkVersion; } private static class ReceiverAndIntentFilter { private final List intentFilterActions; private final String broadcastReceiverClassName; public ReceiverAndIntentFilter(final String broadcastReceiverClassName, final List intentFilterActions) { this.broadcastReceiverClassName = broadcastReceiverClassName; this.intentFilterActions = intentFilterActions; } public String getBroadcastReceiverClassName() { return broadcastReceiverClassName; } public List getIntentFilterActions() { return intentFilterActions; } } }