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