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