• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.Errors.BAD_INDEX;
4 import static org.robolectric.res.android.Util.ALOGI;
5 
6 import org.robolectric.res.android.ResourceTypes.Res_value;
7 import org.robolectric.util.Logger;
8 
9 public class AttributeResolution {
10   public static final boolean kThrowOnBadId = false;
11   private static final boolean kDebugStyles = false;
12 
13   public static final int STYLE_NUM_ENTRIES = 6;
14   public static final int STYLE_TYPE = 0;
15   public static final int STYLE_DATA = 1;
16   public static final int STYLE_ASSET_COOKIE = 2;
17   public static final int STYLE_RESOURCE_ID = 3;
18   public static final int STYLE_CHANGING_CONFIGURATIONS = 4;
19   public static final int STYLE_DENSITY = 5;
20 
21   public static class BagAttributeFinder {
22 
23     private final ResTable.bag_entry[] bag_entries;
24     private final int bagEndIndex;
25 
BagAttributeFinder(ResTable.bag_entry[] bag_entries, int bagEndIndex)26     public BagAttributeFinder(ResTable.bag_entry[] bag_entries, int bagEndIndex) {
27       this.bag_entries = bag_entries;
28       this.bagEndIndex = bagEndIndex;
29     }
30 
find(int curIdent)31     public ResTable.bag_entry find(int curIdent) {
32       for (int curIndex = bagEndIndex - 1; curIndex >= 0; curIndex--) {
33         if (bag_entries[curIndex].map.name.ident == curIdent) {
34           return bag_entries[curIndex];
35         }
36       }
37       return null;
38     }
39   }
40 
41   public static class XmlAttributeFinder {
42 
43     private ResXMLParser xmlParser;
44 
XmlAttributeFinder(ResXMLParser xmlParser)45     public XmlAttributeFinder(ResXMLParser xmlParser) {
46       this.xmlParser = xmlParser;
47     }
48 
find(int curIdent)49     public int find(int curIdent) {
50       if (xmlParser == null) {
51         return 0;
52       }
53 
54       int attributeCount = xmlParser.getAttributeCount();
55       for (int i = 0; i < attributeCount; i++) {
56         if (xmlParser.getAttributeNameResID(i) == curIdent) {
57           return i;
58         }
59       }
60       return attributeCount;
61     }
62   }
63 
ResolveAttrs( ResTableTheme theme, int defStyleAttr, int defStyleRes, int[] srcValues, int srcValuesLength, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)64   public static boolean ResolveAttrs(
65       ResTableTheme theme,
66       int defStyleAttr,
67       int defStyleRes,
68       int[] srcValues,
69       int srcValuesLength,
70       int[] attrs,
71       int attrsLength,
72       int[] outValues,
73       int[] outIndices) {
74     if (kDebugStyles) {
75       ALOGI(
76           "APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x",
77           theme, defStyleAttr, defStyleRes);
78     }
79 
80     final ResTable res = theme.getResTable();
81     ResTable_config config = new ResTable_config();
82     Res_value value;
83 
84     int indicesIdx = 0;
85 
86     // Load default style from attribute, if specified...
87     Ref<Integer> defStyleBagTypeSetFlags = new Ref<>(0);
88     if (defStyleAttr != 0) {
89       Ref<Res_value> valueRef = new Ref<>(null);
90       if (theme.GetAttribute(defStyleAttr, valueRef, defStyleBagTypeSetFlags) >= 0) {
91         value = valueRef.get();
92         if (value.dataType == Res_value.TYPE_REFERENCE) {
93           defStyleRes = value.data;
94         }
95       }
96     }
97 
98     // Now lock down the resource object and start pulling stuff from it.
99     res.lock();
100 
101     // Retrieve the default style bag, if requested.
102     final Ref<ResTable.bag_entry[]> defStyleStart = new Ref<>(null);
103     Ref<Integer> defStyleTypeSetFlags = new Ref<>(0);
104     int bagOff =
105         defStyleRes != 0 ? res.getBagLocked(defStyleRes, defStyleStart, defStyleTypeSetFlags) : -1;
106     defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get());
107     //    const ResTable::bag_entry* const defStyleEnd = defStyleStart + (bagOff >= 0 ? bagOff : 0);
108     final int defStyleEnd = (bagOff >= 0 ? bagOff : 0);
109     BagAttributeFinder defStyleAttrFinder =
110         new BagAttributeFinder(defStyleStart.get(), defStyleEnd);
111 
112     // Now iterate through all of the attributes that the client has requested,
113     // filling in each with whatever data we can find.
114     int destOffset = 0;
115     for (int ii = 0; ii < attrsLength; ii++) {
116       final int curIdent = attrs[ii];
117 
118       if (kDebugStyles) {
119         ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
120       }
121 
122       int block = -1;
123       int typeSetFlags = 0;
124 
125       value = Res_value.NULL_VALUE;
126       config.density = 0;
127 
128       // Try to find a value for this attribute...  we prioritize values
129       // coming from, first XML attributes, then XML style, then default
130       // style, and finally the theme.
131 
132       // Retrieve the current input value if available.
133       if (srcValuesLength > 0 && srcValues[ii] != 0) {
134         value = new Res_value((byte) Res_value.TYPE_ATTRIBUTE, srcValues[ii]);
135         if (kDebugStyles) {
136           ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
137         }
138       } else {
139         final ResTable.bag_entry defStyleEntry = defStyleAttrFinder.find(curIdent);
140         if (defStyleEntry != null) {
141           block = defStyleEntry.stringBlock;
142           typeSetFlags = defStyleTypeSetFlags.get();
143           value = defStyleEntry.map.value;
144           if (kDebugStyles) {
145             ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
146           }
147         }
148       }
149 
150       int resid = 0;
151       Ref<Res_value> valueRef = new Ref<>(value);
152       Ref<Integer> residRef = new Ref<>(resid);
153       Ref<Integer> typeSetFlagsRef = new Ref<>(typeSetFlags);
154       Ref<ResTable_config> configRef = new Ref<>(config);
155       if (value.dataType != Res_value.TYPE_NULL) {
156         // Take care of resolving the found resource to its final value.
157         int newBlock =
158             theme.resolveAttributeReference(valueRef, block, residRef, typeSetFlagsRef, configRef);
159         value = valueRef.get();
160         resid = residRef.get();
161         typeSetFlags = typeSetFlagsRef.get();
162         config = configRef.get();
163         if (newBlock >= 0) block = newBlock;
164         if (kDebugStyles) {
165           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
166         }
167       } else {
168         // If we still don't have a value for this attribute, try to find
169         // it in the theme!
170         int newBlock = theme.GetAttribute(curIdent, valueRef, typeSetFlagsRef);
171         value = valueRef.get();
172         typeSetFlags = typeSetFlagsRef.get();
173 
174         if (newBlock >= 0) {
175           if (kDebugStyles) {
176             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
177           }
178           newBlock = res.resolveReference(valueRef, newBlock, residRef, typeSetFlagsRef, configRef);
179           value = valueRef.get();
180           resid = residRef.get();
181           typeSetFlags = typeSetFlagsRef.get();
182           config = configRef.get();
183           if (kThrowOnBadId) {
184             if (newBlock == BAD_INDEX) {
185               throw new IllegalStateException("Bad resource!");
186             }
187           }
188           if (newBlock >= 0) block = newBlock;
189           if (kDebugStyles) {
190             ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
191           }
192         }
193       }
194 
195       // Deal with the special @null value -- it turns back to TYPE_NULL.
196       if (value.dataType == Res_value.TYPE_REFERENCE && value.data == 0) {
197         if (kDebugStyles) {
198           ALOGI("-> Setting to @null!");
199         }
200         value = Res_value.NULL_VALUE;
201         block = -1;
202       }
203 
204       if (kDebugStyles) {
205         ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data);
206       }
207 
208       // Write the final value back to Java.
209       outValues[destOffset + STYLE_TYPE] = value.dataType;
210       outValues[destOffset + STYLE_DATA] = value.data;
211       outValues[destOffset + STYLE_ASSET_COOKIE] = block != -1 ? res.getTableCookie(block) : -1;
212       outValues[destOffset + STYLE_RESOURCE_ID] = resid;
213       outValues[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
214       outValues[destOffset + STYLE_DENSITY] = config.density;
215 
216       if (outIndices != null && value.dataType != Res_value.TYPE_NULL) {
217         indicesIdx++;
218         outIndices[indicesIdx] = ii;
219       }
220 
221       destOffset += STYLE_NUM_ENTRIES;
222     }
223 
224     res.unlock();
225 
226     if (outIndices != null) {
227       outIndices[0] = indicesIdx;
228     }
229     return true;
230   }
231 
ApplyStyle( ResTableTheme theme, ResXMLParser xmlParser, int defStyleAttr, int defStyleRes, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)232   public static void ApplyStyle(
233       ResTableTheme theme,
234       ResXMLParser xmlParser,
235       int defStyleAttr,
236       int defStyleRes,
237       int[] attrs,
238       int attrsLength,
239       int[] outValues,
240       int[] outIndices) {
241     if (kDebugStyles) {
242       ALOGI(
243           "APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x xml=%s",
244           theme, defStyleAttr, defStyleRes, xmlParser);
245     }
246 
247     final ResTable res = theme.getResTable();
248     Ref<ResTable_config> config = new Ref<>(new ResTable_config());
249     Ref<Res_value> value = new Ref<>(new Res_value());
250 
251     int indices_idx = 0;
252 
253     // Load default style from attribute, if specified...
254     Ref<Integer> defStyleBagTypeSetFlags = new Ref<>(0);
255     if (defStyleAttr != 0) {
256       if (theme.GetAttribute(defStyleAttr, value, defStyleBagTypeSetFlags) >= 0) {
257         if (value.get().dataType == DataType.REFERENCE.code()) {
258           defStyleRes = value.get().data;
259         }
260       }
261     }
262 
263     // Retrieve the style class associated with the current XML tag.
264     int style = 0;
265     Ref<Integer> styleBagTypeSetFlags = new Ref<>(0);
266     if (xmlParser != null) {
267       int idx = xmlParser.indexOfStyle();
268       if (idx >= 0 && xmlParser.getAttributeValue(idx, value) >= 0) {
269         if (value.get().dataType == DataType.ATTRIBUTE.code()) {
270           if (theme.GetAttribute(value.get().data, value, styleBagTypeSetFlags) < 0) {
271             value.set(value.get().withType(DataType.NULL.code()));
272           }
273         }
274         if (value.get().dataType == DataType.REFERENCE.code()) {
275           style = value.get().data;
276         }
277       }
278     }
279 
280     // Now lock down the resource object and start pulling stuff from it.
281     res.lock();
282 
283     // Retrieve the default style bag, if requested.
284     final Ref<ResTable.bag_entry[]> defStyleAttrStart = new Ref<>(null);
285     Ref<Integer> defStyleTypeSetFlags = new Ref<>(0);
286     int bagOff =
287         defStyleRes != 0
288             ? res.getBagLocked(defStyleRes, defStyleAttrStart, defStyleTypeSetFlags)
289             : -1;
290     defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get());
291     // const ResTable::bag_entry* defStyleAttrEnd = defStyleAttrStart + (bagOff >= 0 ? bagOff : 0);
292     final ResTable.bag_entry defStyleAttrEnd = null;
293     // BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart,
294     // defStyleAttrEnd);
295     BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart.get(), bagOff);
296 
297     // Retrieve the style class bag, if requested.
298     final Ref<ResTable.bag_entry[]> styleAttrStart = new Ref<>(null);
299     Ref<Integer> styleTypeSetFlags = new Ref<>(0);
300     bagOff = style != 0 ? res.getBagLocked(style, styleAttrStart, styleTypeSetFlags) : -1;
301     styleTypeSetFlags.set(styleTypeSetFlags.get() | styleBagTypeSetFlags.get());
302     // final ResTable::bag_entry* final styleAttrEnd = styleAttrStart + (bagOff >= 0 ? bagOff : 0);
303     final ResTable.bag_entry styleAttrEnd = null;
304     // BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart, styleAttrEnd);
305     BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart.get(), bagOff);
306 
307     // Retrieve the XML attributes, if requested.
308     final int kXmlBlock = 0x10000000;
309     XmlAttributeFinder xmlAttrFinder = new XmlAttributeFinder(xmlParser);
310     final int xmlAttrEnd = xmlParser != null ? xmlParser.getAttributeCount() : 0;
311 
312     // Now iterate through all of the attributes that the client has requested,
313     // filling in each with whatever data we can find.
314     for (int ii = 0; ii < attrsLength; ii++) {
315       final int curIdent = attrs[ii];
316 
317       if (kDebugStyles) {
318         ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
319       }
320 
321       int block = kXmlBlock;
322       Ref<Integer> typeSetFlags = new Ref<>(0);
323 
324       value.set(Res_value.NULL_VALUE);
325       config.get().density = 0;
326 
327       // Try to find a value for this attribute...  we prioritize values
328       // coming from, first XML attributes, then XML style, then default
329       // style, and finally the theme.
330 
331       // Walk through the xml attributes looking for the requested attribute.
332       final int xmlAttrIdx = xmlAttrFinder.find(curIdent);
333       if (xmlAttrIdx != xmlAttrEnd) {
334         // We found the attribute we were looking for.
335         xmlParser.getAttributeValue(xmlAttrIdx, value);
336         if (kDebugStyles) {
337           ALOGI("-> From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
338         }
339       }
340 
341       if (value.get().dataType == DataType.NULL.code()
342           && value.get().data != Res_value.DATA_NULL_EMPTY) {
343         // Walk through the style class values looking for the requested attribute.
344         final ResTable.bag_entry styleAttrEntry = styleAttrFinder.find(curIdent);
345         if (styleAttrEntry != styleAttrEnd) {
346           // We found the attribute we were looking for.
347           block = styleAttrEntry.stringBlock;
348           typeSetFlags.set(styleTypeSetFlags.get());
349           value.set(styleAttrEntry.map.value);
350           if (kDebugStyles) {
351             ALOGI("-> From style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
352           }
353         }
354       }
355 
356       if (value.get().dataType == DataType.NULL.code()
357           && value.get().data != Res_value.DATA_NULL_EMPTY) {
358         // Walk through the default style values looking for the requested attribute.
359         final ResTable.bag_entry defStyleAttrEntry = defStyleAttrFinder.find(curIdent);
360         if (defStyleAttrEntry != defStyleAttrEnd) {
361           // We found the attribute we were looking for.
362           block = defStyleAttrEntry.stringBlock;
363           typeSetFlags.set(styleTypeSetFlags.get());
364           value.set(defStyleAttrEntry.map.value);
365           if (kDebugStyles) {
366             ALOGI(
367                 "-> From def style: type=0x%x, data=0x%08x",
368                 value.get().dataType, value.get().data);
369           }
370         }
371       }
372 
373       Ref<Integer> resid = new Ref<>(0);
374       if (value.get().dataType != DataType.NULL.code()) {
375         // Take care of resolving the found resource to its final value.
376         int newBlock = theme.resolveAttributeReference(value, block, resid, typeSetFlags, config);
377         if (newBlock >= 0) {
378           block = newBlock;
379         }
380 
381         if (kDebugStyles) {
382           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
383         }
384       } else if (value.get().data != Res_value.DATA_NULL_EMPTY) {
385         // If we still don't have a value for this attribute, try to find it in the theme!
386         int newBlock = theme.GetAttribute(curIdent, value, typeSetFlags);
387         if (newBlock >= 0) {
388           if (kDebugStyles) {
389             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
390           }
391           newBlock = res.resolveReference(value, newBlock, resid, typeSetFlags, config);
392           if (newBlock >= 0) {
393             block = newBlock;
394           }
395 
396           if (kDebugStyles) {
397             ALOGI(
398                 "-> Resolved theme: type=0x%x, data=0x%08x",
399                 value.get().dataType, value.get().data);
400           }
401         }
402       }
403 
404       // Deal with the special @null value -- it turns back to TYPE_NULL.
405       if (value.get().dataType == DataType.REFERENCE.code() && value.get().data == 0) {
406         if (kDebugStyles) {
407           ALOGI(". Setting to @null!");
408         }
409         value.set(Res_value.NULL_VALUE);
410         block = kXmlBlock;
411       }
412 
413       if (kDebugStyles) {
414         ALOGI(
415             "Attribute 0x%08x: type=0x%x, data=0x%08x",
416             curIdent, value.get().dataType, value.get().data);
417       }
418 
419       // Write the final value back to Java.
420       int destIndex = ii * STYLE_NUM_ENTRIES;
421       Res_value res_value = value.get();
422       outValues[destIndex + STYLE_TYPE] = res_value.dataType;
423       outValues[destIndex + STYLE_DATA] = res_value.data;
424       outValues[destIndex + STYLE_ASSET_COOKIE] =
425           block != kXmlBlock ? res.getTableCookie(block) : -1;
426       outValues[destIndex + STYLE_RESOURCE_ID] = resid.get();
427       outValues[destIndex + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
428       outValues[destIndex + STYLE_DENSITY] = config.get().density;
429 
430       if (res_value.dataType != DataType.NULL.code()
431           || res_value.data == Res_value.DATA_NULL_EMPTY) {
432         indices_idx++;
433 
434         // out_indices must NOT be nullptr.
435         outIndices[indices_idx] = ii;
436       }
437 
438       if (res_value.dataType == DataType.ATTRIBUTE.code()) {
439         ResTable.ResourceName attrName = new ResTable.ResourceName();
440         ResTable.ResourceName attrRefName = new ResTable.ResourceName();
441         boolean gotName = res.getResourceName(curIdent, true, attrName);
442         boolean gotRefName = res.getResourceName(res_value.data, true, attrRefName);
443         Logger.warn(
444             "Failed to resolve attribute lookup: %s=\"?%s\"; theme: %s",
445             gotName ? attrName : "unknown", gotRefName ? attrRefName : "unknown", theme);
446       }
447 
448       //      out_values += STYLE_NUM_ENTRIES;
449     }
450 
451     res.unlock();
452 
453     // out_indices must NOT be nullptr.
454     outIndices[0] = indices_idx;
455   }
456 
RetrieveAttributes( ResTable res, ResXMLParser xmlParser, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)457   public static boolean RetrieveAttributes(
458       ResTable res,
459       ResXMLParser xmlParser,
460       int[] attrs,
461       int attrsLength,
462       int[] outValues,
463       int[] outIndices) {
464     Ref<ResTable_config> config = new Ref<>(new ResTable_config());
465     Ref<Res_value> value = new Ref<>(null);
466 
467     int indices_idx = 0;
468 
469     // Now lock down the resource object and start pulling stuff from it.
470     res.lock();
471 
472     // Retrieve the XML attributes, if requested.
473     final int xmlAttrCount = xmlParser.getAttributeCount();
474     int ix = 0;
475     int curXmlAttr = xmlParser.getAttributeNameResID(ix);
476 
477     final int kXmlBlock = 0x10000000;
478 
479     // Now iterate through all of the attributes that the client has requested,
480     // filling in each with whatever data we can find.
481     int baseDest = 0;
482     for (int ii = 0; ii < attrsLength; ii++) {
483       final int curIdent = attrs[ii];
484       int block = 0;
485       Ref<Integer> typeSetFlags = new Ref<>(0);
486 
487       value.set(Res_value.NULL_VALUE);
488       config.get().density = 0;
489 
490       // Try to find a value for this attribute...
491       // Skip through XML attributes until the end or the next possible match.
492       while (ix < xmlAttrCount && curIdent > curXmlAttr) {
493         ix++;
494         curXmlAttr = xmlParser.getAttributeNameResID(ix);
495       }
496       // Retrieve the current XML attribute if it matches, and step to next.
497       if (ix < xmlAttrCount && curIdent == curXmlAttr) {
498         block = kXmlBlock;
499         xmlParser.getAttributeValue(ix, value);
500         ix++;
501         curXmlAttr = xmlParser.getAttributeNameResID(ix);
502       }
503 
504       // printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
505       Ref<Integer> resid = new Ref<>(0);
506       if (value.get().dataType != Res_value.TYPE_NULL) {
507         // Take care of resolving the found resource to its final value.
508         // printf("Resolving attribute reference\n");
509         int newBlock = res.resolveReference(value, block, resid, typeSetFlags, config);
510         if (newBlock >= 0) block = newBlock;
511       }
512 
513       // Deal with the special @null value -- it turns back to TYPE_NULL.
514       if (value.get().dataType == Res_value.TYPE_REFERENCE && value.get().data == 0) {
515         value.set(Res_value.NULL_VALUE);
516         block = kXmlBlock;
517       }
518 
519       // printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType,
520       // value.data);
521 
522       // Write the final value back to Java.
523       outValues[baseDest + STYLE_TYPE] = value.get().dataType;
524       outValues[baseDest + STYLE_DATA] = value.get().data;
525       outValues[baseDest + STYLE_ASSET_COOKIE] =
526           block != kXmlBlock ? res.getTableCookie(block) : -1;
527       outValues[baseDest + STYLE_RESOURCE_ID] = resid.get();
528       outValues[baseDest + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
529       outValues[baseDest + STYLE_DENSITY] = config.get().density;
530 
531       if (outIndices != null
532           && (value.get().dataType != Res_value.TYPE_NULL
533               || value.get().data == Res_value.DATA_NULL_EMPTY)) {
534         indices_idx++;
535         outIndices[indices_idx] = ii;
536       }
537 
538       //      dest += STYLE_NUM_ENTRIES;
539       baseDest += STYLE_NUM_ENTRIES;
540     }
541 
542     res.unlock();
543 
544     if (outIndices != null) {
545       outIndices[0] = indices_idx;
546     }
547 
548     return true;
549   }
550 }
551