• 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
83   // should
84   // predict the rest of the iterations' branch correctly.
85   // TODO(adamlesinski): Run performance tests against these methods and a new, single method
86   // that uses all the sources and branches to the right ones within the inner loop.
87 
88   // `out_values` must NOT be nullptr.
89   // `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)90   public static boolean ResolveAttrs(
91       Theme theme,
92       int def_style_attr,
93       int def_style_res,
94       int[] src_values,
95       int src_values_length,
96       int[] attrs,
97       int attrs_length,
98       int[] out_values,
99       int[] out_indices) {
100     if (kDebugStyles) {
101       ALOGI(
102           "APPLY STYLE: theme=0x%s defStyleAttr=0x%x defStyleRes=0x%x",
103           theme, def_style_attr, def_style_res);
104     }
105 
106     CppAssetManager2 assetmanager = theme.GetAssetManager();
107     ResTable_config config = new ResTable_config();
108     Res_value value;
109 
110     int indicesIdx = 0;
111 
112     // Load default style from attribute, if specified...
113     final Ref<Integer> def_style_flags = new Ref<>(0);
114     if (def_style_attr != 0) {
115       final Ref<Res_value> valueRef = new Ref<>(null);
116       if (theme.GetAttribute(def_style_attr, valueRef, def_style_flags).intValue()
117           != kInvalidCookie) {
118         value = valueRef.get();
119         if (value.dataType == Res_value.TYPE_REFERENCE) {
120           def_style_res = value.data;
121         }
122       }
123     }
124 
125     // Retrieve the default style bag, if requested.
126     ResolvedBag default_style_bag = null;
127     if (def_style_res != 0) {
128       default_style_bag = assetmanager.GetBag(def_style_res);
129       if (default_style_bag != null) {
130         def_style_flags.set(def_style_flags.get() | default_style_bag.type_spec_flags);
131       }
132     }
133     BagAttributeFinder def_style_attr_finder = new BagAttributeFinder(default_style_bag);
134 
135     // Now iterate through all of the attributes that the client has requested,
136     // filling in each with whatever data we can find.
137     int destOffset = 0;
138     for (int ii = 0; ii < attrs_length; ii++) {
139       final int cur_ident = attrs[ii];
140 
141       if (kDebugStyles) {
142         ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
143       }
144 
145       ApkAssetsCookie cookie = K_INVALID_COOKIE;
146       int type_set_flags = 0;
147 
148       value = Res_value.NULL_VALUE;
149       config.density = 0;
150 
151       // Try to find a value for this attribute...  we prioritize values
152       // coming from, first XML attributes, then XML style, then default
153       // style, and finally the theme.
154 
155       // Retrieve the current input value if available.
156       if (src_values_length > 0 && src_values[ii] != 0) {
157         value = new Res_value((byte) Res_value.TYPE_ATTRIBUTE, src_values[ii]);
158         if (kDebugStyles) {
159           ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
160         }
161       } else {
162         final Entry entry = def_style_attr_finder.Find(cur_ident);
163         if (entry != null) {
164           cookie = entry.cookie;
165           type_set_flags = def_style_flags.get();
166           value = entry.value;
167           if (kDebugStyles) {
168             ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
169           }
170         }
171       }
172 
173       int resid = 0;
174       final Ref<Res_value> valueRef = new Ref<>(value);
175       final Ref<Integer> residRef = new Ref<>(resid);
176       final Ref<Integer> type_set_flagsRef = new Ref<>(type_set_flags);
177       final Ref<ResTable_config> configRef = new Ref<>(config);
178       if (value.dataType != Res_value.TYPE_NULL) {
179         // Take care of resolving the found resource to its final value.
180         ApkAssetsCookie new_cookie =
181             theme.ResolveAttributeReference(
182                 cookie, valueRef, configRef, type_set_flagsRef, residRef);
183         if (new_cookie.intValue() != kInvalidCookie) {
184           cookie = new_cookie;
185         }
186         if (kDebugStyles) {
187           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
188         }
189       } else if (value.data != Res_value.DATA_NULL_EMPTY) {
190         // If we still don't have a value for this attribute, try to find it in the theme!
191         ApkAssetsCookie new_cookie = theme.GetAttribute(cur_ident, valueRef, type_set_flagsRef);
192         if (new_cookie.intValue() != kInvalidCookie) {
193           if (kDebugStyles) {
194             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
195           }
196           new_cookie =
197               assetmanager.ResolveReference(
198                   new_cookie, valueRef, configRef, type_set_flagsRef, residRef);
199           if (new_cookie.intValue() != kInvalidCookie) {
200             cookie = new_cookie;
201           }
202           if (kDebugStyles) {
203             ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
204           }
205         }
206       }
207       value = valueRef.get();
208       resid = residRef.get();
209       type_set_flags = type_set_flagsRef.get();
210       config = configRef.get();
211 
212       // Deal with the special @null value -- it turns back to TYPE_NULL.
213       if (value.dataType == Res_value.TYPE_REFERENCE && value.data == 0) {
214         if (kDebugStyles) {
215           ALOGI("-> Setting to @null!");
216         }
217         value = Res_value.NULL_VALUE;
218         cookie = K_INVALID_COOKIE;
219       }
220 
221       if (kDebugStyles) {
222         ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
223       }
224 
225       // Write the final value back to Java.
226       out_values[destOffset + STYLE_TYPE] = value.dataType;
227       out_values[destOffset + STYLE_DATA] = value.data;
228       out_values[destOffset + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
229       out_values[destOffset + STYLE_RESOURCE_ID] = resid;
230       out_values[destOffset + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
231       out_values[destOffset + STYLE_DENSITY] = config.density;
232 
233       if (out_indices != null && value.dataType != Res_value.TYPE_NULL) {
234         indicesIdx++;
235         out_indices[indicesIdx] = ii;
236       }
237 
238       destOffset += STYLE_NUM_ENTRIES;
239     }
240 
241     if (out_indices != null) {
242       out_indices[0] = indicesIdx;
243     }
244     return true;
245   }
246 
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)247   public static void ApplyStyle(
248       Theme theme,
249       ResXMLParser xml_parser,
250       int def_style_attr,
251       int def_style_resid,
252       int[] attrs,
253       int attrs_length,
254       int[] out_values,
255       int[] out_indices) {
256     if (kDebugStyles) {
257       ALOGI(
258           "APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x xml=%s",
259           theme, def_style_attr, def_style_resid, xml_parser);
260     }
261 
262     CppAssetManager2 assetmanager = theme.GetAssetManager();
263     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
264     final Ref<Res_value> value = new Ref<>(new Res_value());
265 
266     int indices_idx = 0;
267 
268     // Load default style from attribute, if specified...
269     final Ref<Integer> def_style_flags = new Ref<>(0);
270     if (def_style_attr != 0) {
271       if (theme.GetAttribute(def_style_attr, value, def_style_flags).intValue() != kInvalidCookie) {
272         if (value.get().dataType == DataType.REFERENCE.code()) {
273           def_style_resid = value.get().data;
274         }
275       }
276     }
277 
278     // Retrieve the style resource ID associated with the current XML tag's style attribute.
279     int style_resid = 0;
280     final Ref<Integer> style_flags = new Ref<>(0);
281     if (xml_parser != null) {
282       int idx = xml_parser.indexOfStyle();
283       if (idx >= 0 && xml_parser.getAttributeValue(idx, value) >= 0) {
284         if (value.get().dataType == DataType.ATTRIBUTE.code()) {
285           // Resolve the attribute with out theme.
286           if (theme.GetAttribute(value.get().data, value, style_flags).intValue()
287               == kInvalidCookie) {
288             value.set(value.get().withType(DataType.NULL.code()));
289           }
290         }
291 
292         if (value.get().dataType == DataType.REFERENCE.code()) {
293           style_resid = value.get().data;
294         }
295       }
296     }
297 
298     // Retrieve the default style bag, if requested.
299     ResolvedBag default_style_bag = null;
300     if (def_style_resid != 0) {
301       default_style_bag = assetmanager.GetBag(def_style_resid);
302       if (default_style_bag != null) {
303         def_style_flags.set(def_style_flags.get() | default_style_bag.type_spec_flags);
304       }
305     }
306 
307     BagAttributeFinder def_style_attr_finder = new BagAttributeFinder(default_style_bag);
308 
309     // Retrieve the style class bag, if requested.
310     ResolvedBag xml_style_bag = null;
311     if (style_resid != 0) {
312       xml_style_bag = assetmanager.GetBag(style_resid);
313       if (xml_style_bag != null) {
314         style_flags.set(style_flags.get() | xml_style_bag.type_spec_flags);
315       }
316     }
317 
318     BagAttributeFinder xml_style_attr_finder = new BagAttributeFinder(xml_style_bag);
319 
320     // Retrieve the XML attributes, if requested.
321     XmlAttributeFinder xml_attr_finder = new XmlAttributeFinder(xml_parser);
322 
323     // Now iterate through all of the attributes that the client has requested,
324     // filling in each with whatever data we can find.
325     for (int ii = 0; ii < attrs_length; ii++) {
326       final int cur_ident = attrs[ii];
327 
328       if (kDebugStyles) {
329         ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
330       }
331 
332       ApkAssetsCookie cookie = K_INVALID_COOKIE;
333       final Ref<Integer> type_set_flags = new Ref<>(0);
334 
335       value.set(Res_value.NULL_VALUE);
336       config.get().density = 0;
337 
338       // Try to find a value for this attribute...  we prioritize values
339       // coming from, first XML attributes, then XML style, then default
340       // style, and finally the theme.
341 
342       // Walk through the xml attributes looking for the requested attribute.
343       int xml_attr_idx = xml_attr_finder.Find(cur_ident);
344       if (xml_attr_idx != -1) {
345         // We found the attribute we were looking for.
346         xml_parser.getAttributeValue(xml_attr_idx, value);
347         type_set_flags.set(style_flags.get());
348         if (kDebugStyles) {
349           ALOGI("-> From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
350         }
351       }
352 
353       if (value.get().dataType == DataType.NULL.code()
354           && value.get().data != Res_value.DATA_NULL_EMPTY) {
355         // Walk through the style class values looking for the requested attribute.
356         Entry entry = xml_style_attr_finder.Find(cur_ident);
357         if (entry != null) {
358           // We found the attribute we were looking for.
359           cookie = entry.cookie;
360           type_set_flags.set(style_flags.get());
361           value.set(entry.value);
362           if (kDebugStyles) {
363             ALOGI("-> From style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
364           }
365         }
366       }
367 
368       if (value.get().dataType == DataType.NULL.code()
369           && value.get().data != Res_value.DATA_NULL_EMPTY) {
370         // Walk through the default style values looking for the requested attribute.
371         Entry entry = def_style_attr_finder.Find(cur_ident);
372         if (entry != null) {
373           // We found the attribute we were looking for.
374           cookie = entry.cookie;
375           type_set_flags.set(def_style_flags.get());
376 
377           value.set(entry.value);
378           if (kDebugStyles) {
379             ALOGI(
380                 "-> From def style: type=0x%x, data=0x%08x",
381                 value.get().dataType, value.get().data);
382           }
383         }
384       }
385 
386       final Ref<Integer> resid = new Ref<>(0);
387       if (value.get().dataType != DataType.NULL.code()) {
388         // Take care of resolving the found resource to its final value.
389         ApkAssetsCookie new_cookie =
390             theme.ResolveAttributeReference(cookie, value, config, type_set_flags, resid);
391         if (new_cookie.intValue() != kInvalidCookie) {
392           cookie = new_cookie;
393         }
394 
395         if (kDebugStyles) {
396           ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
397         }
398       } else if (value.get().data != Res_value.DATA_NULL_EMPTY) {
399         // If we still don't have a value for this attribute, try to find it in the theme!
400         ApkAssetsCookie new_cookie = theme.GetAttribute(cur_ident, value, type_set_flags);
401         if (new_cookie.intValue() != kInvalidCookie) {
402           if (kDebugStyles) {
403             ALOGI("-> From theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
404           }
405           new_cookie =
406               assetmanager.ResolveReference(new_cookie, value, config, type_set_flags, resid);
407           if (new_cookie.intValue() != kInvalidCookie) {
408             cookie = new_cookie;
409           }
410 
411           if (kDebugStyles) {
412             ALOGI(
413                 "-> Resolved theme: type=0x%x, data=0x%08x",
414                 value.get().dataType, value.get().data);
415           }
416         }
417       }
418 
419       // Deal with the special @null value -- it turns back to TYPE_NULL.
420       if (value.get().dataType == DataType.REFERENCE.code() && value.get().data == 0) {
421         if (kDebugStyles) {
422           ALOGI(". Setting to @null!");
423         }
424         value.set(Res_value.NULL_VALUE);
425         cookie = K_INVALID_COOKIE;
426       }
427 
428       if (kDebugStyles) {
429         ALOGI(
430             "Attribute 0x%08x: type=0x%x, data=0x%08x",
431             cur_ident, value.get().dataType, value.get().data);
432       }
433 
434       // Write the final value back to Java.
435       int destIndex = ii * STYLE_NUM_ENTRIES;
436       Res_value res_value = value.get();
437       out_values[destIndex + STYLE_TYPE] = res_value.dataType;
438       out_values[destIndex + STYLE_DATA] = res_value.data;
439       out_values[destIndex + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
440       out_values[destIndex + STYLE_RESOURCE_ID] = resid.get();
441       out_values[destIndex + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags.get();
442       out_values[destIndex + STYLE_DENSITY] = config.get().density;
443 
444       if (res_value.dataType != DataType.NULL.code()
445           || res_value.data == Res_value.DATA_NULL_EMPTY) {
446         indices_idx++;
447 
448         // out_indices must NOT be nullptr.
449         out_indices[indices_idx] = ii;
450       }
451 
452       // Robolectric-custom:
453       // if (false && res_value.dataType == DataType.ATTRIBUTE.code()) {
454       //   final Ref<ResourceName> attrName = new Ref<>(null);
455       //   final Ref<ResourceName> attrRefName = new Ref<>(null);
456       //   boolean gotName = assetmanager.GetResourceName(cur_ident, attrName);
457       //   boolean gotRefName = assetmanager.GetResourceName(res_value.data, attrRefName);
458       //   Logger.warn(
459       //       "Failed to resolve attribute lookup: %s=\"?%s\"; theme: %s",
460       //       gotName ? attrName.get() : "unknown", gotRefName ? attrRefName.get() : "unknown",
461       //       theme);
462       // }
463 
464       //      out_values += STYLE_NUM_ENTRIES;
465     }
466 
467     // out_indices must NOT be nullptr.
468     out_indices[0] = indices_idx;
469   }
470 
RetrieveAttributes( CppAssetManager2 assetmanager, ResXMLParser xml_parser, int[] attrs, int attrs_length, int[] out_values, int[] out_indices)471   public static boolean RetrieveAttributes(
472       CppAssetManager2 assetmanager,
473       ResXMLParser xml_parser,
474       int[] attrs,
475       int attrs_length,
476       int[] out_values,
477       int[] out_indices) {
478     final Ref<ResTable_config> config = new Ref<>(new ResTable_config());
479     final Ref<Res_value> value = new Ref<>(null);
480 
481     int indices_idx = 0;
482 
483     // Retrieve the XML attributes, if requested.
484     final int xml_attr_count = xml_parser.getAttributeCount();
485     int ix = 0;
486     int cur_xml_attr = xml_parser.getAttributeNameResID(ix);
487 
488     // Now iterate through all of the attributes that the client has requested,
489     // filling in each with whatever data we can find.
490     int baseDest = 0;
491     for (int ii = 0; ii < attrs_length; ii++) {
492       final int cur_ident = attrs[ii];
493       ApkAssetsCookie cookie = K_INVALID_COOKIE;
494       final Ref<Integer> type_set_flags = new Ref<>(0);
495 
496       value.set(Res_value.NULL_VALUE);
497       config.get().density = 0;
498 
499       // Try to find a value for this attribute...
500       // Skip through XML attributes until the end or the next possible match.
501       while (ix < xml_attr_count && cur_ident > cur_xml_attr) {
502         ix++;
503         cur_xml_attr = xml_parser.getAttributeNameResID(ix);
504       }
505       // Retrieve the current XML attribute if it matches, and step to next.
506       if (ix < xml_attr_count && cur_ident == cur_xml_attr) {
507         xml_parser.getAttributeValue(ix, value);
508         ix++;
509         cur_xml_attr = xml_parser.getAttributeNameResID(ix);
510       }
511 
512       final Ref<Integer> resid = new Ref<>(0);
513       if (value.get().dataType != Res_value.TYPE_NULL) {
514         // Take care of resolving the found resource to its final value.
515         ApkAssetsCookie new_cookie =
516             assetmanager.ResolveReference(cookie, value, config, type_set_flags, resid);
517         if (new_cookie.intValue() != kInvalidCookie) {
518           cookie = new_cookie;
519         }
520       }
521 
522       // Deal with the special @null value -- it turns back to TYPE_NULL.
523       if (value.get().dataType == Res_value.TYPE_REFERENCE && value.get().data == 0) {
524         value.set(Res_value.NULL_VALUE);
525         cookie = K_INVALID_COOKIE;
526       }
527 
528       // Write the final value back to Java.
529       out_values[baseDest + STYLE_TYPE] = value.get().dataType;
530       out_values[baseDest + STYLE_DATA] = value.get().data;
531       out_values[baseDest + STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
532       out_values[baseDest + STYLE_RESOURCE_ID] = resid.get();
533       out_values[baseDest + STYLE_CHANGING_CONFIGURATIONS] = type_set_flags.get();
534       out_values[baseDest + STYLE_DENSITY] = config.get().density;
535 
536       if (out_indices != null
537           && (value.get().dataType != Res_value.TYPE_NULL
538               || value.get().data == Res_value.DATA_NULL_EMPTY)) {
539         indices_idx++;
540         out_indices[indices_idx] = ii;
541       }
542 
543       //      out_values += STYLE_NUM_ENTRIES;
544       baseDest += STYLE_NUM_ENTRIES;
545     }
546 
547     if (out_indices != null) {
548       out_indices[0] = indices_idx;
549     }
550 
551     return true;
552   }
553 }
554