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