Home | History | Annotate | Download | only in res
      1 package com.xtremelabs.robolectric.res;
      2 
      3 import android.R;
      4 import android.graphics.drawable.AnimationDrawable;
      5 import android.graphics.drawable.Drawable;
      6 import android.graphics.drawable.LayerDrawable;
      7 import android.graphics.drawable.StateListDrawable;
      8 import com.xtremelabs.robolectric.Robolectric;
      9 import com.xtremelabs.robolectric.shadows.ShadowStateListDrawable;
     10 import org.w3c.dom.Document;
     11 import org.w3c.dom.NamedNodeMap;
     12 import org.w3c.dom.Node;
     13 import org.w3c.dom.NodeList;
     14 
     15 import java.io.File;
     16 import java.io.IOException;
     17 import java.util.HashMap;
     18 import java.util.Map;
     19 
     20 /**
     21  * DrawableResourceLoader
     22  */
     23 public class DrawableResourceLoader extends XmlLoader {
     24 
     25     // Put all the states for a StateListDrawable in the into a Map for looking up
     26     // http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList
     27     private static final Map<String, Integer> stateMap = new HashMap<String, Integer>();
     28     static {
     29         stateMap.put("android:state_selected", R.attr.state_selected);
     30         stateMap.put("android:state_pressed", R.attr.state_pressed);
     31         stateMap.put("android:state_focused", R.attr.state_focused);
     32         stateMap.put("android:state_checkable", R.attr.state_checkable);
     33         stateMap.put("android:state_checked", R.attr.state_checked);
     34         stateMap.put("android:state_enabled", R.attr.state_enabled);
     35         stateMap.put("android:state_window_focused", R.attr.state_window_focused);
     36     }
     37 
     38     /** document */
     39     protected Map<String, Document> documents = new HashMap<String, Document>();
     40 
     41     /** resource directory */
     42     protected File resourceDirectory;
     43 
     44     /**
     45      * DrawableResourceLoader constructor.
     46      *
     47      * @param extractor         Extractor
     48      * @param resourceDirectory Resource directory
     49      */
     50     public DrawableResourceLoader(ResourceExtractor extractor, File resourceDirectory) {
     51         super(extractor);
     52         this.resourceDirectory = resourceDirectory;
     53     }
     54 
     55     /**
     56      * Check if resource is xml.
     57      *
     58      * @param resourceId Resource id
     59      * @return Boolean
     60      */
     61     public boolean isXml(int resourceId) {
     62         return documents.containsKey(resourceExtractor.getResourceName(resourceId));
     63     }
     64 
     65     public Drawable getXmlDrawable(int resId) {
     66 
     67         if (!isXml(resId)) {
     68             return null;
     69         }
     70 
     71         Document xmlDoc = documents.get(resourceExtractor.getResourceName(resId));
     72         NodeList nodes = xmlDoc.getElementsByTagName("selector");
     73         if (nodes != null && nodes.getLength() > 0) {
     74             return buildStateListDrawable(xmlDoc);
     75         }
     76 
     77         nodes = xmlDoc.getElementsByTagName("layer-list");
     78         if (nodes != null && nodes.getLength() > 0) {
     79             return new LayerDrawable(null);
     80         }
     81 
     82         nodes = xmlDoc.getElementsByTagName("animation-list");
     83         if (nodes != null && nodes.getLength() > 0) {
     84             return new AnimationDrawable();
     85         }
     86 
     87         return null;
     88     }
     89 
     90     /**
     91      * Store document locally keyed by resource name.
     92      *
     93      * @param xmlFile  Xml file
     94      * @param document Document
     95      * @param isSystem System resource
     96      * @throws Exception
     97      * @see com.xtremelabs.robolectric.res.XmlLoader#processResourceXml(java.io.File,
     98      *      org.w3c.dom.Document, boolean)
     99      */
    100     @Override
    101     protected void processResourceXml(File xmlFile, Document document, boolean isSystem) throws Exception {
    102         String name = toResourceName(xmlFile);
    103         if (!documents.containsKey(name)) {
    104             if (isSystem) {
    105                 name = "android:" + name;
    106             }
    107             documents.put(name, document);
    108         }
    109     }
    110 
    111     /**
    112      * Convert file name to resource name.
    113      *
    114      * @param xmlFile Xml File
    115      * @return Resource name
    116      */
    117     private String toResourceName(File xmlFile) {
    118         try {
    119             return xmlFile.getCanonicalPath().replaceAll("[/\\\\\\\\]", "/")
    120                     .replaceAll("^.*?/res/", "").replaceAll("\\..+$", "");
    121         } catch (IOException e) {
    122             throw new RuntimeException(e);
    123         }
    124     }
    125 
    126 
    127     /**
    128      * Get drawables by resource id.
    129      *
    130      * @param resourceId Resource id
    131      * @return Drawables
    132      */
    133     protected int[] getDrawableIds(int resourceId) {
    134         String resourceName = resourceExtractor.getResourceName(resourceId);
    135         Document document = documents.get(resourceName);
    136 
    137         NodeList items = document.getElementsByTagName("item");
    138         int[] drawableIds = new int[items.getLength()];
    139 
    140         for (int i = 0; i < items.getLength(); i++) {
    141             if (resourceName.startsWith("android:")) {
    142                 drawableIds[i] = -1;
    143             } else {
    144                 Node item = items.item(i);
    145                 Node drawableName = item.getAttributes().getNamedItem("android:drawable");
    146                 if (drawableName != null) {
    147                     drawableIds[i] = resourceExtractor.getResourceId(drawableName.getNodeValue());
    148                 }
    149             }
    150         }
    151 
    152         return drawableIds;
    153     }
    154 
    155     public boolean isAnimationDrawable(int resourceId) {
    156         Document document = documents.get(resourceExtractor.getResourceName(resourceId));
    157         return "animation-list".equals(document.getDocumentElement().getLocalName());
    158     }
    159 
    160     private StateListDrawable buildStateListDrawable(Document d) {
    161         StateListDrawable drawable = new StateListDrawable();
    162         ShadowStateListDrawable shDrawable = Robolectric.shadowOf(drawable);
    163         NodeList items = d.getElementsByTagName("item");
    164         for (int i = 0; i < items.getLength(); i++) {
    165             Node node = items.item(i);
    166             Node drawableName = node.getAttributes().getNamedItem("android:drawable");
    167             if (drawableName != null) {
    168                 int resId = resourceExtractor.getResourceId(drawableName.getNodeValue());
    169                 int stateId = getStateId(node);
    170                 shDrawable.addState(stateId, resId);
    171             }
    172         }
    173         return drawable;
    174     }
    175 
    176     private int getStateId(Node node) {
    177         NamedNodeMap attrs = node.getAttributes();
    178         for (String state : stateMap.keySet()) {
    179             Node attr = attrs.getNamedItem(state);
    180             if (attr != null) {
    181                 return stateMap.get(state);
    182             }
    183         }
    184 
    185         // if a state wasn't specified, return the default state
    186         return R.attr.state_active;
    187     }
    188 }
    189