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