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