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