• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.ApkAssetsCookie.K_INVALID_COOKIE;
4 import static org.robolectric.res.android.ApkAssetsCookie.kInvalidCookie;
5 import static org.robolectric.res.android.Util.ALOGI;
6 
7 import java.util.Arrays;
8 import org.robolectric.res.android.CppAssetManager2.ResolvedBag;
9 import org.robolectric.res.android.CppAssetManager2.ResolvedBag.Entry;
10 import org.robolectric.res.android.CppAssetManager2.Theme;
11 import org.robolectric.res.android.ResourceTypes.Res_value;
12 
13 // transliterated from
14 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/AttributeResolution.cpp and
15 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/AttributeResolution.h
16 
17 public class AttributeResolution9 {
18   public static final boolean kThrowOnBadId = false;
19   private static final boolean kDebugStyles = false;
20 
21   // Offsets into the outValues array populated by the methods below. outValues is a uint32_t
22   // array, but each logical element takes up 6 uint32_t-sized physical elements.
23   public static final int STYLE_NUM_ENTRIES = 6;
24   public static final int STYLE_TYPE = 0;
25   public static final int STYLE_DATA = 1;
26   public static final int STYLE_ASSET_COOKIE = 2;
27   public static final int STYLE_RESOURCE_ID = 3;
28   public static final int STYLE_CHANGING_CONFIGURATIONS = 4;
29   public static final int STYLE_DENSITY = 5;
30 
31   // Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie)32   private static int ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
33     return cookie.intValue() != kInvalidCookie ? (cookie.intValue() + 1) : -1;
34   }
35 
36   public static class XmlAttributeFinder {
37 
38     private ResXMLParser xmlParser;
39 
XmlAttributeFinder(ResXMLParser xmlParser)40     XmlAttributeFinder(ResXMLParser xmlParser) {
41       this.xmlParser = xmlParser;
42     }
43 
Find(int curIdent)44     public int Find(int curIdent) {
45       if (xmlParser == null) {
46         return -1;
47       }
48 
49       int attributeCount = xmlParser.getAttributeCount();
50       for (int i = 0; i < attributeCount; i++) {
51         if (xmlParser.getAttributeNameResID(i) == curIdent) {
52           return i;
53         }
54       }
55       return -1;
56     }
57   }
58 
59   public static class BagAttributeFinder {
60     private final Entry[] bagEntries;
61 
BagAttributeFinder(ResolvedBag bag)62     BagAttributeFinder(ResolvedBag bag) {
63       this.bagEntries = bag == null ? null : bag.entries;
64     }
65 
66     // Robolectric: unoptimized relative to Android impl
Find(int ident)67     Entry Find(int ident) {
68       Entry needle = new Entry();
69       needle.key = ident;
70 
71       if (bagEntries == null) {
72         return null;
73       }
74 
75       int i = Arrays.binarySearch(bagEntries, needle, (o1, o2) -> o1.key - o2.key);
76       return i < 0 ? null : bagEntries[i];
77     }
78   }
79 
80   // These are all variations of the same method. They each perform the exact same operation,
81   // but on various data sources. I *think* they are re-written to avoid an extra branch
82   // in the inner loop, but after one branch miss (some pointer != null), the branch predictor should
83   // predict the rest of the iterations' branch correctly.
84   // TODO(adamlesinski): Run performance tests against these methods and a new, single method
85   // that uses all the sources and branches to the right ones within the inner loop.
86 
87   // `out_values` must NOT be nullptr.
88   // `out_indices` may be nullptr.
ResolveAttrs(Theme theme, int def_style_attr, int def_style_res, int[] src_values, int src_values_length, int[] attrs, int attrs_length, int[] out_values, int[] out_indices)89   public static boolean ResolveAttrs(Theme theme, int def_style_attr,
90                                      int def_style_res, int[] src_values,
91                                      int src_values_length, int[] attrs,
92                                      int attrs_length, int[] out_values, int[] out_indices) {
93     if (kDebugStyles) {
94       ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme,
95           def_style_attr, def_style_res);
96     }
97 
98     CppAssetManager2 assetmanager = theme.GetAssetManager();
99     ResTable_config config = new ResTable_config();
100     Res_value value;
101 
102     int indicesIdx = 0;
103 
104     // Load default style from attribute, if specified...
105     final Ref<Integer> def_style_flags = new Ref<>(0);
106     if (def_style_attr != 0) {
107       final Ref<Res_value> valueRef = new Ref<>(null);
108       if (theme.GetAttribute(def_style_attr, valueRef, def_style_flags).intValue() != kInvalidCookie) {
109         value = valueRef.get();
110         if (value.dataType == Res_value.TYPE_REFERENCE) {
111           def_style_res = value.data;
112         }
113       }
114     }
115 
116     // Retrieve the default style bag, if requested.
117     ResolvedBag default_style_bag = null;
118     if (def_style_res != 0) {
119       default_style_bag = assetmanager.GetBag(def_style_res);
120       if (default_style_bag != null) {
121         def_style_flags.set(def_style_flags.get() | default_style_bag.type_spec_flags);
122       }
123     }
124     BagAttributeFinder def_style_attr_finder = new BagAttributeFinder(default_style_bag);
125 
126     // Now iterate through all of the attributes that the client has requested,
127     // filling in each with whatever data we can find.
128     int destOffset = 0;
129     for (int ii=0; ii<attrs_length; ii++) {
130       final int cur_ident = attrs[ii];
131 
132       if (kDebugStyles) {
133         ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
134       }
135 
136       ApkAssetsCookie cookie = K_INVALID_COOKIE;
137       int type_set_flags = 0;
138 
139       value = Res_value.NULL_VALUE;
140       config.density = 0;
141 
142       // Try to find a value for this attribute...  we prioritize values
143       // coming from, first XML attributes, then XML style, then default
144       // style, and finally the theme.
145 
146       // Retrieve the current input value if available.
147       if (src_values_length > 0 && src_values[ii] != 0) {
148         value = new Res_value((byte) Res_value.TYPE_ATTRIBUTE, src_values[ii]);
149         if (kDebugStyles) {
150           ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
151         }
152       } else {
153         final Entry entry = def_style_attr_finder.Find(cur_ident);
154         if (entry != null) {
155           cookie = entry.cookie;
156           type_set_flags = def_style_flags.get();
157           value = entry.value;
158           if (kDebugStyles) {
159             ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
160           }
161         }
162       }
163 
164       int resid = 0;
165       final Ref<Res_value> valueRef = new Ref<>(value);
166       final Ref<Integer> residRef = new Ref<>(resid);
167       final Ref<Integer> type_set_flagsRef = new Ref<>(type_set_flags);
168       final Ref<ResTable_config> configRef = new Ref<>(config);
169       if (value.dataType != Res_value.TYPE_NULL) {
170         // Take care of resolving the found resource to its final value.
171         ApkAssetsCookie new_cookie =
172             theme.ResolveAttributeReference(cookie, valueRef, configRef, type_set_flagsRef, residRef);
173         if (new_cookie.intValue() != kInvalidCookie) {
174           cookie = new_cookie;
175         }
176         if (kDebugStyles) {
177           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
178         }
179       } else if (value.data != Res_value.DATA_NULL_EMPTY) {
180         // If we still don't have a value for this attribute, try to find it in the theme!
181         ApkAssetsCookie new_cookie = theme.GetAttribute(cur_ident, valueRef, type_set_flagsRef);
182         if (new_cookie.intValue() != kInvalidCookie) {
183           if (kDebugStyles) {
184             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
185           }
186           new_cookie =
187               assetmanager.ResolveReference(new_cookie, valueRef, configRef, type_set_flagsRef, residRef);
188           if (new_cookie.intValue() != kInvalidCookie) {
189             cookie = new_cookie;
190           }
191           if (kDebugStyles) {
192             ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
193           }
194         }
195       }
196       value = valueRef.get();
197       resid = residRef.get();
198       type_set_flags = type_set_flagsRef.get();
199       config = configRef.get();
200 
201       // Deal with the special @null value -- it turns back to TYPE_NULL.
202       if (value.dataType == Res_value.TYPE_REFERENCE && value.data == 0) {
203         if (kDebugStyles) {
204           ALOGI("-> Setting to @null!");
205         }
206         value = Res_value.NULL_VALUE;
207         cookie = K_INVALID_COOKIE;
208       }
209 
210       if (kDebugStyles) {
211         ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType,
212             value.data);
213       }
214 
215       // Write the final value back to Java.
216       out_values[destOffset + STYLE_TYPE] = value.dataType;
217       out_values[destOffset + STYLE_DATA] = value.data;
218       out_values[destOffset + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
219       out_values[destOffset + STYLE_RESOURCE_ID] = resid;
220       out_values[destOffset + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
221       out_values[destOffset + STYLE_DENSITY] = config.density;
222 
223       if (out_indices != null && value.dataType != Res_value.TYPE_NULL) {
224         indicesIdx++;
225         out_indices[indicesIdx] = ii;
226       }
227 
228       destOffset += STYLE_NUM_ENTRIES;
229     }
230 
231     if (out_indices != null) {
232       out_indices[0] = indicesIdx;
233     }
234     return true;
235   }
236 
ApplyStyle(Theme theme, ResXMLParser xml_parser, int def_style_attr, int def_style_resid, int[] attrs, int attrs_length, int[] out_values, int[] out_indices)237   public static void ApplyStyle(Theme theme, ResXMLParser xml_parser, int def_style_attr,
238                                 int def_style_resid, int[] attrs, int attrs_length,
239                                 int[] out_values, int[] out_indices) {
240     if (kDebugStyles) {
241       ALOGI("APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x xml=%s",
242           theme, def_style_attr, def_style_resid, xml_parser);
243     }
244 
245     CppAssetManager2 assetmanager = theme.GetAssetManager();
246     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
247     final Ref<Res_value> value = new Ref<>(new Res_value());
248 
249     int indices_idx = 0;
250 
251     // Load default style from attribute, if specified...
252     final Ref<Integer> def_style_flags = new Ref<>(0);
253     if (def_style_attr != 0) {
254       if (theme.GetAttribute(def_style_attr, value, def_style_flags).intValue() != kInvalidCookie) {
255         if (value.get().dataType == DataType.REFERENCE.code()) {
256           def_style_resid = value.get().data;
257         }
258       }
259     }
260 
261     // Retrieve the style resource ID associated with the current XML tag's style attribute.
262     int style_resid = 0;
263     final Ref<Integer> style_flags = new Ref<>(0);
264     if (xml_parser != null) {
265       int idx = xml_parser.indexOfStyle();
266       if (idx >= 0 && xml_parser.getAttributeValue(idx, value) >= 0) {
267         if (value.get().dataType == DataType.ATTRIBUTE.code()) {
268           // Resolve the attribute with out theme.
269           if (theme.GetAttribute(value.get().data, value, style_flags).intValue() == kInvalidCookie) {
270             value.set(value.get().withType(DataType.NULL.code()));
271           }
272         }
273 
274         if (value.get().dataType == DataType.REFERENCE.code()) {
275           style_resid = value.get().data;
276         }
277       }
278     }
279 
280     // Retrieve the default style bag, if requested.
281     ResolvedBag default_style_bag = null;
282     if (def_style_resid != 0) {
283       default_style_bag = assetmanager.GetBag(def_style_resid);
284       if (default_style_bag != null) {
285         def_style_flags.set(def_style_flags.get() | default_style_bag.type_spec_flags);
286       }
287     }
288 
289     BagAttributeFinder def_style_attr_finder = new BagAttributeFinder(default_style_bag);
290 
291     // Retrieve the style class bag, if requested.
292     ResolvedBag xml_style_bag = null;
293     if (style_resid != 0) {
294       xml_style_bag = assetmanager.GetBag(style_resid);
295       if (xml_style_bag != null) {
296         style_flags.set(style_flags.get() | xml_style_bag.type_spec_flags);
297       }
298     }
299 
300     BagAttributeFinder xml_style_attr_finder = new BagAttributeFinder(xml_style_bag);
301 
302     // Retrieve the XML attributes, if requested.
303     XmlAttributeFinder xml_attr_finder = new XmlAttributeFinder(xml_parser);
304 
305     // Now iterate through all of the attributes that the client has requested,
306     // filling in each with whatever data we can find.
307     for (int ii = 0; ii < attrs_length; ii++) {
308       final int cur_ident = attrs[ii];
309 
310       if (kDebugStyles) {
311         ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
312       }
313 
314       ApkAssetsCookie cookie = K_INVALID_COOKIE;
315       final Ref<Integer> type_set_flags = new Ref<>(0);
316 
317       value.set(Res_value.NULL_VALUE);
318       config.get().density = 0;
319 
320       // Try to find a value for this attribute...  we prioritize values
321       // coming from, first XML attributes, then XML style, then default
322       // style, and finally the theme.
323 
324       // Walk through the xml attributes looking for the requested attribute.
325       int xml_attr_idx = xml_attr_finder.Find(cur_ident);
326       if (xml_attr_idx != -1) {
327         // We found the attribute we were looking for.
328         xml_parser.getAttributeValue(xml_attr_idx, value);
329         type_set_flags.set(style_flags.get());
330         if (kDebugStyles) {
331           ALOGI("-> From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
332         }
333       }
334 
335       if (value.get().dataType == DataType.NULL.code() && value.get().data != Res_value.DATA_NULL_EMPTY) {
336         // Walk through the style class values looking for the requested attribute.
337         Entry entry = xml_style_attr_finder.Find(cur_ident);
338         if (entry != null) {
339           // We found the attribute we were looking for.
340           cookie = entry.cookie;
341           type_set_flags.set(style_flags.get());
342           value.set(entry.value);
343           if (kDebugStyles) {
344             ALOGI("-> From style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
345           }
346         }
347       }
348 
349       if (value.get().dataType == DataType.NULL.code() && value.get().data != Res_value.DATA_NULL_EMPTY) {
350         // Walk through the default style values looking for the requested attribute.
351         Entry entry = def_style_attr_finder.Find(cur_ident);
352         if (entry != null) {
353           // We found the attribute we were looking for.
354           cookie = entry.cookie;
355           type_set_flags.set(def_style_flags.get());
356 
357           value.set(entry.value);
358           if (kDebugStyles) {
359             ALOGI("-> From def style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
360           }
361         }
362       }
363 
364       final Ref<Integer> resid = new Ref<>(0);
365       if (value.get().dataType != DataType.NULL.code()) {
366         // Take care of resolving the found resource to its final value.
367         ApkAssetsCookie new_cookie =
368             theme.ResolveAttributeReference(cookie, value, config, type_set_flags, resid);
369         if (new_cookie.intValue() != kInvalidCookie) {
370           cookie = new_cookie;
371         }
372 
373         if (kDebugStyles) {
374           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
375         }
376       } else if (value.get().data != Res_value.DATA_NULL_EMPTY) {
377         // If we still don't have a value for this attribute, try to find it in the theme!
378         ApkAssetsCookie new_cookie = theme.GetAttribute(cur_ident, value, type_set_flags);
379         if (new_cookie.intValue() != kInvalidCookie) {
380           if (kDebugStyles) {
381             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
382           }
383           new_cookie =
384               assetmanager.ResolveReference(new_cookie, value, config, type_set_flags, resid);
385           if (new_cookie.intValue() != kInvalidCookie) {
386             cookie = new_cookie;
387           }
388 
389           if (kDebugStyles) {
390             ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
391           }
392         }
393       }
394 
395       // Deal with the special @null value -- it turns back to TYPE_NULL.
396       if (value.get().dataType == DataType.REFERENCE.code() && value.get().data == 0) {
397         if (kDebugStyles) {
398           ALOGI(". Setting to @null!");
399         }
400         value.set(Res_value.NULL_VALUE);
401         cookie = K_INVALID_COOKIE;
402       }
403 
404       if (kDebugStyles) {
405         ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.get().dataType, value.get().data);
406       }
407 
408       // Write the final value back to Java.
409       int destIndex = ii * STYLE_NUM_ENTRIES;
410       Res_value res_value = value.get();
411       out_values[destIndex + STYLE_TYPE] = res_value.dataType;
412       out_values[destIndex + STYLE_DATA] = res_value.data;
413       out_values[destIndex + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
414       out_values[destIndex + STYLE_RESOURCE_ID] = resid.get();
415       out_values[destIndex + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags.get();
416       out_values[destIndex + STYLE_DENSITY] = config.get().density;
417 
418       if (res_value.dataType != DataType.NULL.code() || res_value.data == Res_value.DATA_NULL_EMPTY) {
419         indices_idx++;
420 
421         // out_indices must NOT be nullptr.
422         out_indices[indices_idx] = ii;
423       }
424 
425       // Robolectric-custom:
426       // if (false && res_value.dataType == DataType.ATTRIBUTE.code()) {
427       //   final Ref<ResourceName> attrName = new Ref<>(null);
428       //   final Ref<ResourceName> attrRefName = new Ref<>(null);
429       //   boolean gotName = assetmanager.GetResourceName(cur_ident, attrName);
430       //   boolean gotRefName = assetmanager.GetResourceName(res_value.data, attrRefName);
431       //   Logger.warn(
432       //       "Failed to resolve attribute lookup: %s=\"?%s\"; theme: %s",
433       //       gotName ? attrName.get() : "unknown", gotRefName ? attrRefName.get() : "unknown",
434       //       theme);
435       // }
436 
437 //      out_values += STYLE_NUM_ENTRIES;
438     }
439 
440     // out_indices must NOT be nullptr.
441     out_indices[0] = indices_idx;
442   }
443 
RetrieveAttributes(CppAssetManager2 assetmanager, ResXMLParser xml_parser, int[] attrs, int attrs_length, int[] out_values, int[] out_indices)444   public static boolean RetrieveAttributes(CppAssetManager2 assetmanager, ResXMLParser xml_parser, int[] attrs,
445       int attrs_length, int[] out_values, int[] out_indices) {
446     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
447     final Ref<Res_value> value = new Ref<>(null);
448 
449     int indices_idx = 0;
450 
451     // Retrieve the XML attributes, if requested.
452     final int xml_attr_count = xml_parser.getAttributeCount();
453     int ix = 0;
454     int cur_xml_attr = xml_parser.getAttributeNameResID(ix);
455 
456     // Now iterate through all of the attributes that the client has requested,
457     // filling in each with whatever data we can find.
458     int baseDest = 0;
459     for (int ii = 0; ii < attrs_length; ii++) {
460       final int cur_ident = attrs[ii];
461       ApkAssetsCookie cookie = K_INVALID_COOKIE;
462       final Ref<Integer> type_set_flags = new Ref<>(0);
463 
464       value.set(Res_value.NULL_VALUE);
465       config.get().density = 0;
466 
467       // Try to find a value for this attribute...
468       // Skip through XML attributes until the end or the next possible match.
469       while (ix < xml_attr_count && cur_ident > cur_xml_attr) {
470         ix++;
471         cur_xml_attr = xml_parser.getAttributeNameResID(ix);
472       }
473       // Retrieve the current XML attribute if it matches, and step to next.
474       if (ix < xml_attr_count && cur_ident == cur_xml_attr) {
475         xml_parser.getAttributeValue(ix, value);
476         ix++;
477         cur_xml_attr = xml_parser.getAttributeNameResID(ix);
478       }
479 
480       final Ref<Integer> resid = new Ref<>(0);
481       if (value.get().dataType != Res_value.TYPE_NULL) {
482         // Take care of resolving the found resource to its final value.
483         ApkAssetsCookie new_cookie =
484             assetmanager.ResolveReference(cookie, value, config, type_set_flags, resid);
485         if (new_cookie.intValue() != kInvalidCookie) {
486           cookie = new_cookie;
487         }
488       }
489 
490       // Deal with the special @null value -- it turns back to TYPE_NULL.
491       if (value.get().dataType == Res_value.TYPE_REFERENCE && value.get().data == 0) {
492         value.set(Res_value.NULL_VALUE);
493         cookie = K_INVALID_COOKIE;
494       }
495 
496       // Write the final value back to Java.
497       out_values[baseDest + STYLE_TYPE] = value.get().dataType;
498       out_values[baseDest + STYLE_DATA] = value.get().data;
499       out_values[baseDest + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
500       out_values[baseDest + STYLE_RESOURCE_ID] = resid.get();
501       out_values[baseDest + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags.get();
502       out_values[baseDest + STYLE_DENSITY] = config.get().density;
503 
504       if (out_indices != null &&
505           (value.get().dataType != Res_value.TYPE_NULL
506               || value.get().data == Res_value.DATA_NULL_EMPTY)) {
507         indices_idx++;
508         out_indices[indices_idx] = ii;
509       }
510 
511 //      out_values += STYLE_NUM_ENTRIES;
512       baseDest += STYLE_NUM_ENTRIES;
513     }
514 
515     if (out_indices != null) {
516       out_indices[0] = indices_idx;
517     }
518 
519     return true;
520   }
521 }
522