1 package org.robolectric.res; 2 3 import java.io.IOException; 4 import java.lang.reflect.Field; 5 import java.lang.reflect.Modifier; 6 import org.robolectric.util.Logger; 7 import org.robolectric.util.PerfStatsCollector; 8 9 public class ResourceTableFactory { 10 /** Builds an Android framework resource table in the "android" package space. */ newFrameworkResourceTable(ResourcePath resourcePath)11 public PackageResourceTable newFrameworkResourceTable(ResourcePath resourcePath) { 12 return PerfStatsCollector.getInstance() 13 .measure( 14 "load legacy framework resources", 15 () -> { 16 PackageResourceTable resourceTable = new PackageResourceTable("android"); 17 18 if (resourcePath.getRClass() != null) { 19 addRClassValues(resourceTable, resourcePath.getRClass()); 20 addMissingStyleableAttributes(resourceTable, resourcePath.getRClass()); 21 } 22 if (resourcePath.getInternalRClass() != null) { 23 addRClassValues(resourceTable, resourcePath.getInternalRClass()); 24 addMissingStyleableAttributes(resourceTable, resourcePath.getInternalRClass()); 25 } 26 27 parseResourceFiles(resourcePath, resourceTable); 28 29 return resourceTable; 30 }); 31 } 32 33 /** 34 * Creates an application resource table which can be constructed with multiple resources paths 35 * representing overlayed resource libraries. 36 */ newResourceTable(String packageName, ResourcePath... resourcePaths)37 public PackageResourceTable newResourceTable(String packageName, ResourcePath... resourcePaths) { 38 return PerfStatsCollector.getInstance() 39 .measure( 40 "load legacy app resources", 41 () -> { 42 PackageResourceTable resourceTable = new PackageResourceTable(packageName); 43 44 for (ResourcePath resourcePath : resourcePaths) { 45 if (resourcePath.getRClass() != null) { 46 addRClassValues(resourceTable, resourcePath.getRClass()); 47 } 48 } 49 50 for (ResourcePath resourcePath : resourcePaths) { 51 parseResourceFiles(resourcePath, resourceTable); 52 } 53 54 return resourceTable; 55 }); 56 } 57 58 private void addRClassValues(PackageResourceTable resourceTable, Class<?> rClass) { 59 for (Class innerClass : rClass.getClasses()) { 60 String resourceType = innerClass.getSimpleName(); 61 if (!resourceType.equals("styleable")) { 62 for (Field field : innerClass.getDeclaredFields()) { 63 if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) { 64 int id; 65 try { 66 id = field.getInt(null); 67 } catch (IllegalAccessException e) { 68 throw new RuntimeException(e); 69 } 70 String resourceName = field.getName(); 71 // Pre-release versions of Android use the resource value '0' to indicate that the 72 // resource is being staged for inclusion in the public Android SDK. Skip these 73 // resource ids. 74 if (id == 0) { 75 Logger.debug("Ignoring staged resource " + resourceName); 76 continue; 77 } 78 resourceTable.addResource(id, resourceType, resourceName); 79 } 80 } 81 } 82 } 83 } 84 85 /** 86 * Check the stylable elements. Not for aapt generated R files but for framework R files it is possible to 87 * have attributes in the styleable array for which there is no corresponding R.attr field. 88 */ 89 private void addMissingStyleableAttributes(PackageResourceTable resourceTable, Class<?> rClass) { 90 for (Class innerClass : rClass.getClasses()) { 91 if (innerClass.getSimpleName().equals("styleable")) { 92 String styleableName = null; // Current styleable name 93 int[] styleableArray = null; // Current styleable value array or references 94 for (Field field : innerClass.getDeclaredFields()) { 95 if (field.getType().equals(int[].class) && Modifier.isStatic(field.getModifiers())) { 96 styleableName = field.getName(); 97 try { 98 styleableArray = (int[]) field.get(null); 99 } catch (IllegalAccessException e) { 100 throw new RuntimeException(e); 101 } 102 } else if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) { 103 String attributeName = field.getName().substring(styleableName.length() + 1); 104 try { 105 int styleableIndex = field.getInt(null); 106 int attributeResId = styleableArray[styleableIndex]; 107 resourceTable.addResource(attributeResId, "attr", attributeName); 108 } catch (IllegalAccessException e) { 109 throw new RuntimeException(e); 110 } 111 } 112 } 113 } 114 } 115 } 116 117 private void parseResourceFiles(ResourcePath resourcePath, PackageResourceTable resourceTable) { 118 if (!resourcePath.hasResources()) { 119 Logger.debug("No resources for %s", resourceTable.getPackageName()); 120 return; 121 } 122 123 Logger.debug( 124 "Loading resources for %s from %s...", 125 resourceTable.getPackageName(), resourcePath.getResourceBase()); 126 127 try { 128 new StaxDocumentLoader( 129 resourceTable.getPackageName(), 130 resourcePath.getResourceBase(), 131 new NodeHandler() 132 .addHandler( 133 "resources", 134 new NodeHandler() 135 .addHandler( 136 "bool", new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN)) 137 .addHandler( 138 "item[@type='bool']", 139 new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN)) 140 .addHandler( 141 "color", new StaxValueLoader(resourceTable, "color", ResType.COLOR)) 142 .addHandler( 143 "item[@type='color']", 144 new StaxValueLoader(resourceTable, "color", ResType.COLOR)) 145 .addHandler( 146 "drawable", 147 new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE)) 148 .addHandler( 149 "item[@type='drawable']", 150 new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE)) 151 .addHandler( 152 "item[@type='mipmap']", 153 new StaxValueLoader(resourceTable, "mipmap", ResType.DRAWABLE)) 154 .addHandler( 155 "dimen", new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN)) 156 .addHandler( 157 "item[@type='dimen']", 158 new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN)) 159 .addHandler( 160 "integer", 161 new StaxValueLoader(resourceTable, "integer", ResType.INTEGER)) 162 .addHandler( 163 "item[@type='integer']", 164 new StaxValueLoader(resourceTable, "integer", ResType.INTEGER)) 165 .addHandler( 166 "integer-array", 167 new StaxArrayLoader( 168 resourceTable, "array", ResType.INTEGER_ARRAY, ResType.INTEGER)) 169 .addHandler( 170 "fraction", 171 new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION)) 172 .addHandler( 173 "item[@type='fraction']", 174 new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION)) 175 .addHandler( 176 "item[@type='layout']", 177 new StaxValueLoader(resourceTable, "layout", ResType.LAYOUT)) 178 .addHandler( 179 "plurals", 180 new StaxPluralsLoader( 181 resourceTable, "plurals", ResType.CHAR_SEQUENCE)) 182 .addHandler( 183 "string", 184 new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE)) 185 .addHandler( 186 "item[@type='string']", 187 new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE)) 188 .addHandler( 189 "string-array", 190 new StaxArrayLoader( 191 resourceTable, 192 "array", 193 ResType.CHAR_SEQUENCE_ARRAY, 194 ResType.CHAR_SEQUENCE)) 195 .addHandler( 196 "array", 197 new StaxArrayLoader( 198 resourceTable, "array", ResType.TYPED_ARRAY, null)) 199 .addHandler( 200 "id", new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE)) 201 .addHandler( 202 "item[@type='id']", 203 new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE)) 204 .addHandler( 205 "attr", new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA)) 206 .addHandler( 207 "declare-styleable", 208 new NodeHandler() 209 .addHandler( 210 "attr", 211 new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA))) 212 .addHandler( 213 "style", new StaxStyleLoader(resourceTable, "style", ResType.STYLE)))) 214 .load("values"); 215 216 loadOpaque(resourcePath, resourceTable, "layout", ResType.LAYOUT); 217 loadOpaque(resourcePath, resourceTable, "menu", ResType.LAYOUT); 218 loadOpaque(resourcePath, resourceTable, "drawable", ResType.DRAWABLE); 219 loadOpaque(resourcePath, resourceTable, "mipmap", ResType.DRAWABLE); 220 loadOpaque(resourcePath, resourceTable, "anim", ResType.LAYOUT); 221 loadOpaque(resourcePath, resourceTable, "animator", ResType.LAYOUT); 222 loadOpaque(resourcePath, resourceTable, "color", ResType.COLOR_STATE_LIST); 223 loadOpaque(resourcePath, resourceTable, "xml", ResType.LAYOUT); 224 loadOpaque(resourcePath, resourceTable, "transition", ResType.LAYOUT); 225 loadOpaque(resourcePath, resourceTable, "interpolator", ResType.LAYOUT); 226 227 new DrawableResourceLoader(resourceTable).findDrawableResources(resourcePath); 228 new RawResourceLoader(resourcePath).loadTo(resourceTable); 229 } catch (Exception e) { 230 throw new RuntimeException(e); 231 } 232 } 233 234 private void loadOpaque( 235 ResourcePath resourcePath, 236 final PackageResourceTable resourceTable, 237 final String type, 238 final ResType resType) 239 throws IOException { 240 new DocumentLoader(resourceTable.getPackageName(), resourcePath.getResourceBase()) { 241 @Override 242 protected void loadResourceXmlFile(XmlContext xmlContext) { 243 resourceTable.addResource( 244 type, 245 Fs.baseNameFor(xmlContext.getXmlFile()), 246 new FileTypedResource(xmlContext.getXmlFile(), resType, xmlContext)); 247 } 248 }.load(type); 249 } 250 } 251