1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Errors.BAD_INDEX; 4 import static org.robolectric.res.android.Util.ALOGI; 5 6 import org.robolectric.res.android.ResourceTypes.Res_value; 7 import org.robolectric.util.Logger; 8 9 public class AttributeResolution { 10 public static final boolean kThrowOnBadId = false; 11 private static final boolean kDebugStyles = false; 12 13 public static final int STYLE_NUM_ENTRIES = 6; 14 public static final int STYLE_TYPE = 0; 15 public static final int STYLE_DATA = 1; 16 public static final int STYLE_ASSET_COOKIE = 2; 17 public static final int STYLE_RESOURCE_ID = 3; 18 public static final int STYLE_CHANGING_CONFIGURATIONS = 4; 19 public static final int STYLE_DENSITY = 5; 20 21 public static class BagAttributeFinder { 22 23 private final ResTable.bag_entry[] bag_entries; 24 private final int bagEndIndex; 25 BagAttributeFinder(ResTable.bag_entry[] bag_entries, int bagEndIndex)26 public BagAttributeFinder(ResTable.bag_entry[] bag_entries, int bagEndIndex) { 27 this.bag_entries = bag_entries; 28 this.bagEndIndex = bagEndIndex; 29 } 30 find(int curIdent)31 public ResTable.bag_entry find(int curIdent) { 32 for (int curIndex = bagEndIndex - 1; curIndex >= 0; curIndex--) { 33 if (bag_entries[curIndex].map.name.ident == curIdent) { 34 return bag_entries[curIndex]; 35 } 36 } 37 return null; 38 } 39 } 40 41 public static class XmlAttributeFinder { 42 43 private ResXMLParser xmlParser; 44 XmlAttributeFinder(ResXMLParser xmlParser)45 public XmlAttributeFinder(ResXMLParser xmlParser) { 46 this.xmlParser = xmlParser; 47 } 48 find(int curIdent)49 public int find(int curIdent) { 50 if (xmlParser == null) { 51 return 0; 52 } 53 54 int attributeCount = xmlParser.getAttributeCount(); 55 for (int i = 0; i < attributeCount; i++) { 56 if (xmlParser.getAttributeNameResID(i) == curIdent) { 57 return i; 58 } 59 } 60 return attributeCount; 61 } 62 } 63 ResolveAttrs( ResTableTheme theme, int defStyleAttr, int defStyleRes, int[] srcValues, int srcValuesLength, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)64 public static boolean ResolveAttrs( 65 ResTableTheme theme, 66 int defStyleAttr, 67 int defStyleRes, 68 int[] srcValues, 69 int srcValuesLength, 70 int[] attrs, 71 int attrsLength, 72 int[] outValues, 73 int[] outIndices) { 74 if (kDebugStyles) { 75 ALOGI( 76 "APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x", 77 theme, defStyleAttr, defStyleRes); 78 } 79 80 final ResTable res = theme.getResTable(); 81 ResTable_config config = new ResTable_config(); 82 Res_value value; 83 84 int indicesIdx = 0; 85 86 // Load default style from attribute, if specified... 87 Ref<Integer> defStyleBagTypeSetFlags = new Ref<>(0); 88 if (defStyleAttr != 0) { 89 Ref<Res_value> valueRef = new Ref<>(null); 90 if (theme.GetAttribute(defStyleAttr, valueRef, defStyleBagTypeSetFlags) >= 0) { 91 value = valueRef.get(); 92 if (value.dataType == Res_value.TYPE_REFERENCE) { 93 defStyleRes = value.data; 94 } 95 } 96 } 97 98 // Now lock down the resource object and start pulling stuff from it. 99 res.lock(); 100 101 // Retrieve the default style bag, if requested. 102 final Ref<ResTable.bag_entry[]> defStyleStart = new Ref<>(null); 103 Ref<Integer> defStyleTypeSetFlags = new Ref<>(0); 104 int bagOff = 105 defStyleRes != 0 ? res.getBagLocked(defStyleRes, defStyleStart, defStyleTypeSetFlags) : -1; 106 defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get()); 107 // const ResTable::bag_entry* const defStyleEnd = defStyleStart + (bagOff >= 0 ? bagOff : 0); 108 final int defStyleEnd = (bagOff >= 0 ? bagOff : 0); 109 BagAttributeFinder defStyleAttrFinder = 110 new BagAttributeFinder(defStyleStart.get(), defStyleEnd); 111 112 // Now iterate through all of the attributes that the client has requested, 113 // filling in each with whatever data we can find. 114 int destOffset = 0; 115 for (int ii = 0; ii < attrsLength; ii++) { 116 final int curIdent = attrs[ii]; 117 118 if (kDebugStyles) { 119 ALOGI("RETRIEVING ATTR 0x%08x...", curIdent); 120 } 121 122 int block = -1; 123 int typeSetFlags = 0; 124 125 value = Res_value.NULL_VALUE; 126 config.density = 0; 127 128 // Try to find a value for this attribute... we prioritize values 129 // coming from, first XML attributes, then XML style, then default 130 // style, and finally the theme. 131 132 // Retrieve the current input value if available. 133 if (srcValuesLength > 0 && srcValues[ii] != 0) { 134 value = new Res_value((byte) Res_value.TYPE_ATTRIBUTE, srcValues[ii]); 135 if (kDebugStyles) { 136 ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); 137 } 138 } else { 139 final ResTable.bag_entry defStyleEntry = defStyleAttrFinder.find(curIdent); 140 if (defStyleEntry != null) { 141 block = defStyleEntry.stringBlock; 142 typeSetFlags = defStyleTypeSetFlags.get(); 143 value = defStyleEntry.map.value; 144 if (kDebugStyles) { 145 ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); 146 } 147 } 148 } 149 150 int resid = 0; 151 Ref<Res_value> valueRef = new Ref<>(value); 152 Ref<Integer> residRef = new Ref<>(resid); 153 Ref<Integer> typeSetFlagsRef = new Ref<>(typeSetFlags); 154 Ref<ResTable_config> configRef = new Ref<>(config); 155 if (value.dataType != Res_value.TYPE_NULL) { 156 // Take care of resolving the found resource to its final value. 157 int newBlock = 158 theme.resolveAttributeReference(valueRef, block, residRef, typeSetFlagsRef, configRef); 159 value = valueRef.get(); 160 resid = residRef.get(); 161 typeSetFlags = typeSetFlagsRef.get(); 162 config = configRef.get(); 163 if (newBlock >= 0) block = newBlock; 164 if (kDebugStyles) { 165 ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); 166 } 167 } else { 168 // If we still don't have a value for this attribute, try to find 169 // it in the theme! 170 int newBlock = theme.GetAttribute(curIdent, valueRef, typeSetFlagsRef); 171 value = valueRef.get(); 172 typeSetFlags = typeSetFlagsRef.get(); 173 174 if (newBlock >= 0) { 175 if (kDebugStyles) { 176 ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); 177 } 178 newBlock = res.resolveReference(valueRef, newBlock, residRef, typeSetFlagsRef, configRef); 179 value = valueRef.get(); 180 resid = residRef.get(); 181 typeSetFlags = typeSetFlagsRef.get(); 182 config = configRef.get(); 183 if (kThrowOnBadId) { 184 if (newBlock == BAD_INDEX) { 185 throw new IllegalStateException("Bad resource!"); 186 } 187 } 188 if (newBlock >= 0) block = newBlock; 189 if (kDebugStyles) { 190 ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); 191 } 192 } 193 } 194 195 // Deal with the special @null value -- it turns back to TYPE_NULL. 196 if (value.dataType == Res_value.TYPE_REFERENCE && value.data == 0) { 197 if (kDebugStyles) { 198 ALOGI("-> Setting to @null!"); 199 } 200 value = Res_value.NULL_VALUE; 201 block = -1; 202 } 203 204 if (kDebugStyles) { 205 ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data); 206 } 207 208 // Write the final value back to Java. 209 outValues[destOffset + STYLE_TYPE] = value.dataType; 210 outValues[destOffset + STYLE_DATA] = value.data; 211 outValues[destOffset + STYLE_ASSET_COOKIE] = block != -1 ? res.getTableCookie(block) : -1; 212 outValues[destOffset + STYLE_RESOURCE_ID] = resid; 213 outValues[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; 214 outValues[destOffset + STYLE_DENSITY] = config.density; 215 216 if (outIndices != null && value.dataType != Res_value.TYPE_NULL) { 217 indicesIdx++; 218 outIndices[indicesIdx] = ii; 219 } 220 221 destOffset += STYLE_NUM_ENTRIES; 222 } 223 224 res.unlock(); 225 226 if (outIndices != null) { 227 outIndices[0] = indicesIdx; 228 } 229 return true; 230 } 231 ApplyStyle( ResTableTheme theme, ResXMLParser xmlParser, int defStyleAttr, int defStyleRes, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)232 public static void ApplyStyle( 233 ResTableTheme theme, 234 ResXMLParser xmlParser, 235 int defStyleAttr, 236 int defStyleRes, 237 int[] attrs, 238 int attrsLength, 239 int[] outValues, 240 int[] outIndices) { 241 if (kDebugStyles) { 242 ALOGI( 243 "APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x xml=%s", 244 theme, defStyleAttr, defStyleRes, xmlParser); 245 } 246 247 final ResTable res = theme.getResTable(); 248 Ref<ResTable_config> config = new Ref<>(new ResTable_config()); 249 Ref<Res_value> value = new Ref<>(new Res_value()); 250 251 int indices_idx = 0; 252 253 // Load default style from attribute, if specified... 254 Ref<Integer> defStyleBagTypeSetFlags = new Ref<>(0); 255 if (defStyleAttr != 0) { 256 if (theme.GetAttribute(defStyleAttr, value, defStyleBagTypeSetFlags) >= 0) { 257 if (value.get().dataType == DataType.REFERENCE.code()) { 258 defStyleRes = value.get().data; 259 } 260 } 261 } 262 263 // Retrieve the style class associated with the current XML tag. 264 int style = 0; 265 Ref<Integer> styleBagTypeSetFlags = new Ref<>(0); 266 if (xmlParser != null) { 267 int idx = xmlParser.indexOfStyle(); 268 if (idx >= 0 && xmlParser.getAttributeValue(idx, value) >= 0) { 269 if (value.get().dataType == DataType.ATTRIBUTE.code()) { 270 if (theme.GetAttribute(value.get().data, value, styleBagTypeSetFlags) < 0) { 271 value.set(value.get().withType(DataType.NULL.code())); 272 } 273 } 274 if (value.get().dataType == DataType.REFERENCE.code()) { 275 style = value.get().data; 276 } 277 } 278 } 279 280 // Now lock down the resource object and start pulling stuff from it. 281 res.lock(); 282 283 // Retrieve the default style bag, if requested. 284 final Ref<ResTable.bag_entry[]> defStyleAttrStart = new Ref<>(null); 285 Ref<Integer> defStyleTypeSetFlags = new Ref<>(0); 286 int bagOff = 287 defStyleRes != 0 288 ? res.getBagLocked(defStyleRes, defStyleAttrStart, defStyleTypeSetFlags) 289 : -1; 290 defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get()); 291 // const ResTable::bag_entry* defStyleAttrEnd = defStyleAttrStart + (bagOff >= 0 ? bagOff : 0); 292 final ResTable.bag_entry defStyleAttrEnd = null; 293 // BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart, 294 // defStyleAttrEnd); 295 BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart.get(), bagOff); 296 297 // Retrieve the style class bag, if requested. 298 final Ref<ResTable.bag_entry[]> styleAttrStart = new Ref<>(null); 299 Ref<Integer> styleTypeSetFlags = new Ref<>(0); 300 bagOff = style != 0 ? res.getBagLocked(style, styleAttrStart, styleTypeSetFlags) : -1; 301 styleTypeSetFlags.set(styleTypeSetFlags.get() | styleBagTypeSetFlags.get()); 302 // final ResTable::bag_entry* final styleAttrEnd = styleAttrStart + (bagOff >= 0 ? bagOff : 0); 303 final ResTable.bag_entry styleAttrEnd = null; 304 // BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart, styleAttrEnd); 305 BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart.get(), bagOff); 306 307 // Retrieve the XML attributes, if requested. 308 final int kXmlBlock = 0x10000000; 309 XmlAttributeFinder xmlAttrFinder = new XmlAttributeFinder(xmlParser); 310 final int xmlAttrEnd = xmlParser != null ? xmlParser.getAttributeCount() : 0; 311 312 // Now iterate through all of the attributes that the client has requested, 313 // filling in each with whatever data we can find. 314 for (int ii = 0; ii < attrsLength; ii++) { 315 final int curIdent = attrs[ii]; 316 317 if (kDebugStyles) { 318 ALOGI("RETRIEVING ATTR 0x%08x...", curIdent); 319 } 320 321 int block = kXmlBlock; 322 Ref<Integer> typeSetFlags = new Ref<>(0); 323 324 value.set(Res_value.NULL_VALUE); 325 config.get().density = 0; 326 327 // Try to find a value for this attribute... we prioritize values 328 // coming from, first XML attributes, then XML style, then default 329 // style, and finally the theme. 330 331 // Walk through the xml attributes looking for the requested attribute. 332 final int xmlAttrIdx = xmlAttrFinder.find(curIdent); 333 if (xmlAttrIdx != xmlAttrEnd) { 334 // We found the attribute we were looking for. 335 xmlParser.getAttributeValue(xmlAttrIdx, value); 336 if (kDebugStyles) { 337 ALOGI("-> From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data); 338 } 339 } 340 341 if (value.get().dataType == DataType.NULL.code() 342 && value.get().data != Res_value.DATA_NULL_EMPTY) { 343 // Walk through the style class values looking for the requested attribute. 344 final ResTable.bag_entry styleAttrEntry = styleAttrFinder.find(curIdent); 345 if (styleAttrEntry != styleAttrEnd) { 346 // We found the attribute we were looking for. 347 block = styleAttrEntry.stringBlock; 348 typeSetFlags.set(styleTypeSetFlags.get()); 349 value.set(styleAttrEntry.map.value); 350 if (kDebugStyles) { 351 ALOGI("-> From style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data); 352 } 353 } 354 } 355 356 if (value.get().dataType == DataType.NULL.code() 357 && value.get().data != Res_value.DATA_NULL_EMPTY) { 358 // Walk through the default style values looking for the requested attribute. 359 final ResTable.bag_entry defStyleAttrEntry = defStyleAttrFinder.find(curIdent); 360 if (defStyleAttrEntry != defStyleAttrEnd) { 361 // We found the attribute we were looking for. 362 block = defStyleAttrEntry.stringBlock; 363 typeSetFlags.set(styleTypeSetFlags.get()); 364 value.set(defStyleAttrEntry.map.value); 365 if (kDebugStyles) { 366 ALOGI( 367 "-> From def style: type=0x%x, data=0x%08x", 368 value.get().dataType, value.get().data); 369 } 370 } 371 } 372 373 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 int newBlock = theme.resolveAttributeReference(value, block, resid, typeSetFlags, config); 377 if (newBlock >= 0) { 378 block = newBlock; 379 } 380 381 if (kDebugStyles) { 382 ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.get().dataType, value.get().data); 383 } 384 } else if (value.get().data != Res_value.DATA_NULL_EMPTY) { 385 // If we still don't have a value for this attribute, try to find it in the theme! 386 int newBlock = theme.GetAttribute(curIdent, value, typeSetFlags); 387 if (newBlock >= 0) { 388 if (kDebugStyles) { 389 ALOGI("-> From theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data); 390 } 391 newBlock = res.resolveReference(value, newBlock, resid, typeSetFlags, config); 392 if (newBlock >= 0) { 393 block = newBlock; 394 } 395 396 if (kDebugStyles) { 397 ALOGI( 398 "-> Resolved theme: type=0x%x, data=0x%08x", 399 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 block = kXmlBlock; 411 } 412 413 if (kDebugStyles) { 414 ALOGI( 415 "Attribute 0x%08x: type=0x%x, data=0x%08x", 416 curIdent, value.get().dataType, value.get().data); 417 } 418 419 // Write the final value back to Java. 420 int destIndex = ii * STYLE_NUM_ENTRIES; 421 Res_value res_value = value.get(); 422 outValues[destIndex + STYLE_TYPE] = res_value.dataType; 423 outValues[destIndex + STYLE_DATA] = res_value.data; 424 outValues[destIndex + STYLE_ASSET_COOKIE] = 425 block != kXmlBlock ? res.getTableCookie(block) : -1; 426 outValues[destIndex + STYLE_RESOURCE_ID] = resid.get(); 427 outValues[destIndex + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get(); 428 outValues[destIndex + STYLE_DENSITY] = config.get().density; 429 430 if (res_value.dataType != DataType.NULL.code() 431 || res_value.data == Res_value.DATA_NULL_EMPTY) { 432 indices_idx++; 433 434 // out_indices must NOT be nullptr. 435 outIndices[indices_idx] = ii; 436 } 437 438 if (res_value.dataType == DataType.ATTRIBUTE.code()) { 439 ResTable.ResourceName attrName = new ResTable.ResourceName(); 440 ResTable.ResourceName attrRefName = new ResTable.ResourceName(); 441 boolean gotName = res.getResourceName(curIdent, true, attrName); 442 boolean gotRefName = res.getResourceName(res_value.data, true, attrRefName); 443 Logger.warn( 444 "Failed to resolve attribute lookup: %s=\"?%s\"; theme: %s", 445 gotName ? attrName : "unknown", gotRefName ? attrRefName : "unknown", theme); 446 } 447 448 // out_values += STYLE_NUM_ENTRIES; 449 } 450 451 res.unlock(); 452 453 // out_indices must NOT be nullptr. 454 outIndices[0] = indices_idx; 455 } 456 RetrieveAttributes( ResTable res, ResXMLParser xmlParser, int[] attrs, int attrsLength, int[] outValues, int[] outIndices)457 public static boolean RetrieveAttributes( 458 ResTable res, 459 ResXMLParser xmlParser, 460 int[] attrs, 461 int attrsLength, 462 int[] outValues, 463 int[] outIndices) { 464 Ref<ResTable_config> config = new Ref<>(new ResTable_config()); 465 Ref<Res_value> value = new Ref<>(null); 466 467 int indices_idx = 0; 468 469 // Now lock down the resource object and start pulling stuff from it. 470 res.lock(); 471 472 // Retrieve the XML attributes, if requested. 473 final int xmlAttrCount = xmlParser.getAttributeCount(); 474 int ix = 0; 475 int curXmlAttr = xmlParser.getAttributeNameResID(ix); 476 477 final int kXmlBlock = 0x10000000; 478 479 // Now iterate through all of the attributes that the client has requested, 480 // filling in each with whatever data we can find. 481 int baseDest = 0; 482 for (int ii = 0; ii < attrsLength; ii++) { 483 final int curIdent = attrs[ii]; 484 int block = 0; 485 Ref<Integer> typeSetFlags = new Ref<>(0); 486 487 value.set(Res_value.NULL_VALUE); 488 config.get().density = 0; 489 490 // Try to find a value for this attribute... 491 // Skip through XML attributes until the end or the next possible match. 492 while (ix < xmlAttrCount && curIdent > curXmlAttr) { 493 ix++; 494 curXmlAttr = xmlParser.getAttributeNameResID(ix); 495 } 496 // Retrieve the current XML attribute if it matches, and step to next. 497 if (ix < xmlAttrCount && curIdent == curXmlAttr) { 498 block = kXmlBlock; 499 xmlParser.getAttributeValue(ix, value); 500 ix++; 501 curXmlAttr = xmlParser.getAttributeNameResID(ix); 502 } 503 504 // printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); 505 Ref<Integer> resid = new Ref<>(0); 506 if (value.get().dataType != Res_value.TYPE_NULL) { 507 // Take care of resolving the found resource to its final value. 508 // printf("Resolving attribute reference\n"); 509 int newBlock = res.resolveReference(value, block, resid, typeSetFlags, config); 510 if (newBlock >= 0) block = newBlock; 511 } 512 513 // Deal with the special @null value -- it turns back to TYPE_NULL. 514 if (value.get().dataType == Res_value.TYPE_REFERENCE && value.get().data == 0) { 515 value.set(Res_value.NULL_VALUE); 516 block = kXmlBlock; 517 } 518 519 // printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, 520 // value.data); 521 522 // Write the final value back to Java. 523 outValues[baseDest + STYLE_TYPE] = value.get().dataType; 524 outValues[baseDest + STYLE_DATA] = value.get().data; 525 outValues[baseDest + STYLE_ASSET_COOKIE] = 526 block != kXmlBlock ? res.getTableCookie(block) : -1; 527 outValues[baseDest + STYLE_RESOURCE_ID] = resid.get(); 528 outValues[baseDest + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get(); 529 outValues[baseDest + STYLE_DENSITY] = config.get().density; 530 531 if (outIndices != null 532 && (value.get().dataType != Res_value.TYPE_NULL 533 || value.get().data == Res_value.DATA_NULL_EMPTY)) { 534 indices_idx++; 535 outIndices[indices_idx] = ii; 536 } 537 538 // dest += STYLE_NUM_ENTRIES; 539 baseDest += STYLE_NUM_ENTRIES; 540 } 541 542 res.unlock(); 543 544 if (outIndices != null) { 545 outIndices[0] = indices_idx; 546 } 547 548 return true; 549 } 550 } 551