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