• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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