Home | History | Annotate | Download | only in robolectric
      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