1package com.xtremelabs.robolectric.res;
2
3import android.R;
4import android.graphics.drawable.AnimationDrawable;
5import android.graphics.drawable.Drawable;
6import android.graphics.drawable.LayerDrawable;
7import android.graphics.drawable.StateListDrawable;
8import com.xtremelabs.robolectric.Robolectric;
9import com.xtremelabs.robolectric.shadows.ShadowStateListDrawable;
10import org.w3c.dom.Document;
11import org.w3c.dom.NamedNodeMap;
12import org.w3c.dom.Node;
13import org.w3c.dom.NodeList;
14
15import java.io.File;
16import java.io.IOException;
17import java.util.HashMap;
18import java.util.Map;
19
20/**
21 * DrawableResourceLoader
22 */
23public 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