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