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