
org.robolectric.res.android.AttributeResolution Maven / Gradle / Ivy
package org.robolectric.res.android;
import static org.robolectric.res.android.Errors.BAD_INDEX;
import static org.robolectric.res.android.Util.ALOGI;
import org.robolectric.res.android.ResourceTypes.Res_value;
import org.robolectric.util.Logger;
public class AttributeResolution {
public static final boolean kThrowOnBadId = false;
private static final boolean kDebugStyles = false;
public static final int STYLE_NUM_ENTRIES = 6;
public static final int STYLE_TYPE = 0;
public static final int STYLE_DATA = 1;
public static final int STYLE_ASSET_COOKIE = 2;
public static final int STYLE_RESOURCE_ID = 3;
public static final int STYLE_CHANGING_CONFIGURATIONS = 4;
public static final int STYLE_DENSITY = 5;
public static class BagAttributeFinder {
private final ResTable.bag_entry[] bag_entries;
private final int bagEndIndex;
public BagAttributeFinder(ResTable.bag_entry[] bag_entries, int bagEndIndex) {
this.bag_entries = bag_entries;
this.bagEndIndex = bagEndIndex;
}
public ResTable.bag_entry find(int curIdent) {
for (int curIndex = bagEndIndex - 1; curIndex >= 0; curIndex--) {
if (bag_entries[curIndex].map.name.ident == curIdent) {
return bag_entries[curIndex];
}
}
return null;
}
}
public static class XmlAttributeFinder {
private ResXMLParser xmlParser;
public XmlAttributeFinder(ResXMLParser xmlParser) {
this.xmlParser = xmlParser;
}
public int find(int curIdent) {
if (xmlParser == null) {
return 0;
}
int attributeCount = xmlParser.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
if (xmlParser.getAttributeNameResID(i) == curIdent) {
return i;
}
}
return attributeCount;
}
}
public static boolean ResolveAttrs(
ResTableTheme theme,
int defStyleAttr,
int defStyleRes,
int[] srcValues,
int srcValuesLength,
int[] attrs,
int attrsLength,
int[] outValues,
int[] outIndices) {
if (kDebugStyles) {
ALOGI(
"APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x",
theme, defStyleAttr, defStyleRes);
}
final ResTable res = theme.getResTable();
ResTable_config config = new ResTable_config();
Res_value value;
int indicesIdx = 0;
// Load default style from attribute, if specified...
Ref defStyleBagTypeSetFlags = new Ref<>(0);
if (defStyleAttr != 0) {
Ref valueRef = new Ref<>(null);
if (theme.GetAttribute(defStyleAttr, valueRef, defStyleBagTypeSetFlags) >= 0) {
value = valueRef.get();
if (value.dataType == Res_value.TYPE_REFERENCE) {
defStyleRes = value.data;
}
}
}
// Now lock down the resource object and start pulling stuff from it.
res.lock();
// Retrieve the default style bag, if requested.
final Ref defStyleStart = new Ref<>(null);
Ref defStyleTypeSetFlags = new Ref<>(0);
int bagOff =
defStyleRes != 0 ? res.getBagLocked(defStyleRes, defStyleStart, defStyleTypeSetFlags) : -1;
defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get());
// const ResTable::bag_entry* const defStyleEnd = defStyleStart + (bagOff >= 0 ? bagOff : 0);
final int defStyleEnd = (bagOff >= 0 ? bagOff : 0);
BagAttributeFinder defStyleAttrFinder =
new BagAttributeFinder(defStyleStart.get(), defStyleEnd);
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
int destOffset = 0;
for (int ii = 0; ii < attrsLength; ii++) {
final int curIdent = attrs[ii];
if (kDebugStyles) {
ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
}
int block = -1;
int typeSetFlags = 0;
value = Res_value.NULL_VALUE;
config.density = 0;
// Try to find a value for this attribute... we prioritize values
// coming from, first XML attributes, then XML style, then default
// style, and finally the theme.
// Retrieve the current input value if available.
if (srcValuesLength > 0 && srcValues[ii] != 0) {
value = new Res_value((byte) Res_value.TYPE_ATTRIBUTE, srcValues[ii]);
if (kDebugStyles) {
ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else {
final ResTable.bag_entry defStyleEntry = defStyleAttrFinder.find(curIdent);
if (defStyleEntry != null) {
block = defStyleEntry.stringBlock;
typeSetFlags = defStyleTypeSetFlags.get();
value = defStyleEntry.map.value;
if (kDebugStyles) {
ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
int resid = 0;
Ref valueRef = new Ref<>(value);
Ref residRef = new Ref<>(resid);
Ref typeSetFlagsRef = new Ref<>(typeSetFlags);
Ref configRef = new Ref<>(config);
if (value.dataType != Res_value.TYPE_NULL) {
// Take care of resolving the found resource to its final value.
int newBlock =
theme.resolveAttributeReference(valueRef, block, residRef, typeSetFlagsRef, configRef);
value = valueRef.get();
resid = residRef.get();
typeSetFlags = typeSetFlagsRef.get();
config = configRef.get();
if (newBlock >= 0) block = newBlock;
if (kDebugStyles) {
ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else {
// If we still don't have a value for this attribute, try to find
// it in the theme!
int newBlock = theme.GetAttribute(curIdent, valueRef, typeSetFlagsRef);
value = valueRef.get();
typeSetFlags = typeSetFlagsRef.get();
if (newBlock >= 0) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
newBlock = res.resolveReference(valueRef, newBlock, residRef, typeSetFlagsRef, configRef);
value = valueRef.get();
resid = residRef.get();
typeSetFlags = typeSetFlagsRef.get();
config = configRef.get();
if (kThrowOnBadId) {
if (newBlock == BAD_INDEX) {
throw new IllegalStateException("Bad resource!");
}
}
if (newBlock >= 0) block = newBlock;
if (kDebugStyles) {
ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.dataType == Res_value.TYPE_REFERENCE && value.data == 0) {
if (kDebugStyles) {
ALOGI("-> Setting to @null!");
}
value = Res_value.NULL_VALUE;
block = -1;
}
if (kDebugStyles) {
ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data);
}
// Write the final value back to Java.
outValues[destOffset + STYLE_TYPE] = value.dataType;
outValues[destOffset + STYLE_DATA] = value.data;
outValues[destOffset + STYLE_ASSET_COOKIE] = block != -1 ? res.getTableCookie(block) : -1;
outValues[destOffset + STYLE_RESOURCE_ID] = resid;
outValues[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
outValues[destOffset + STYLE_DENSITY] = config.density;
if (outIndices != null && value.dataType != Res_value.TYPE_NULL) {
indicesIdx++;
outIndices[indicesIdx] = ii;
}
destOffset += STYLE_NUM_ENTRIES;
}
res.unlock();
if (outIndices != null) {
outIndices[0] = indicesIdx;
}
return true;
}
public static void ApplyStyle(
ResTableTheme theme,
ResXMLParser xmlParser,
int defStyleAttr,
int defStyleRes,
int[] attrs,
int attrsLength,
int[] outValues,
int[] outIndices) {
if (kDebugStyles) {
ALOGI(
"APPLY STYLE: theme=%s defStyleAttr=0x%x defStyleRes=0x%x xml=%s",
theme, defStyleAttr, defStyleRes, xmlParser);
}
final ResTable res = theme.getResTable();
Ref config = new Ref<>(new ResTable_config());
Ref value = new Ref<>(new Res_value());
int indices_idx = 0;
// Load default style from attribute, if specified...
Ref defStyleBagTypeSetFlags = new Ref<>(0);
if (defStyleAttr != 0) {
if (theme.GetAttribute(defStyleAttr, value, defStyleBagTypeSetFlags) >= 0) {
if (value.get().dataType == DataType.REFERENCE.code()) {
defStyleRes = value.get().data;
}
}
}
// Retrieve the style class associated with the current XML tag.
int style = 0;
Ref styleBagTypeSetFlags = new Ref<>(0);
if (xmlParser != null) {
int idx = xmlParser.indexOfStyle();
if (idx >= 0 && xmlParser.getAttributeValue(idx, value) >= 0) {
if (value.get().dataType == DataType.ATTRIBUTE.code()) {
if (theme.GetAttribute(value.get().data, value, styleBagTypeSetFlags) < 0) {
value.set(value.get().withType(DataType.NULL.code()));
}
}
if (value.get().dataType == DataType.REFERENCE.code()) {
style = value.get().data;
}
}
}
// Now lock down the resource object and start pulling stuff from it.
res.lock();
// Retrieve the default style bag, if requested.
final Ref defStyleAttrStart = new Ref<>(null);
Ref defStyleTypeSetFlags = new Ref<>(0);
int bagOff =
defStyleRes != 0
? res.getBagLocked(defStyleRes, defStyleAttrStart, defStyleTypeSetFlags)
: -1;
defStyleTypeSetFlags.set(defStyleTypeSetFlags.get() | defStyleBagTypeSetFlags.get());
// const ResTable::bag_entry* defStyleAttrEnd = defStyleAttrStart + (bagOff >= 0 ? bagOff : 0);
final ResTable.bag_entry defStyleAttrEnd = null;
// BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart,
// defStyleAttrEnd);
BagAttributeFinder defStyleAttrFinder = new BagAttributeFinder(defStyleAttrStart.get(), bagOff);
// Retrieve the style class bag, if requested.
final Ref styleAttrStart = new Ref<>(null);
Ref styleTypeSetFlags = new Ref<>(0);
bagOff = style != 0 ? res.getBagLocked(style, styleAttrStart, styleTypeSetFlags) : -1;
styleTypeSetFlags.set(styleTypeSetFlags.get() | styleBagTypeSetFlags.get());
// final ResTable::bag_entry* final styleAttrEnd = styleAttrStart + (bagOff >= 0 ? bagOff : 0);
final ResTable.bag_entry styleAttrEnd = null;
// BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart, styleAttrEnd);
BagAttributeFinder styleAttrFinder = new BagAttributeFinder(styleAttrStart.get(), bagOff);
// Retrieve the XML attributes, if requested.
final int kXmlBlock = 0x10000000;
XmlAttributeFinder xmlAttrFinder = new XmlAttributeFinder(xmlParser);
final int xmlAttrEnd = xmlParser != null ? xmlParser.getAttributeCount() : 0;
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
for (int ii = 0; ii < attrsLength; ii++) {
final int curIdent = attrs[ii];
if (kDebugStyles) {
ALOGI("RETRIEVING ATTR 0x%08x...", curIdent);
}
int block = kXmlBlock;
Ref typeSetFlags = new Ref<>(0);
value.set(Res_value.NULL_VALUE);
config.get().density = 0;
// Try to find a value for this attribute... we prioritize values
// coming from, first XML attributes, then XML style, then default
// style, and finally the theme.
// Walk through the xml attributes looking for the requested attribute.
final int xmlAttrIdx = xmlAttrFinder.find(curIdent);
if (xmlAttrIdx != xmlAttrEnd) {
// We found the attribute we were looking for.
xmlParser.getAttributeValue(xmlAttrIdx, value);
if (kDebugStyles) {
ALOGI("-> From XML: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
}
}
if (value.get().dataType == DataType.NULL.code()
&& value.get().data != Res_value.DATA_NULL_EMPTY) {
// Walk through the style class values looking for the requested attribute.
final ResTable.bag_entry styleAttrEntry = styleAttrFinder.find(curIdent);
if (styleAttrEntry != styleAttrEnd) {
// We found the attribute we were looking for.
block = styleAttrEntry.stringBlock;
typeSetFlags.set(styleTypeSetFlags.get());
value.set(styleAttrEntry.map.value);
if (kDebugStyles) {
ALOGI("-> From style: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
}
}
}
if (value.get().dataType == DataType.NULL.code()
&& value.get().data != Res_value.DATA_NULL_EMPTY) {
// Walk through the default style values looking for the requested attribute.
final ResTable.bag_entry defStyleAttrEntry = defStyleAttrFinder.find(curIdent);
if (defStyleAttrEntry != defStyleAttrEnd) {
// We found the attribute we were looking for.
block = defStyleAttrEntry.stringBlock;
typeSetFlags.set(styleTypeSetFlags.get());
value.set(defStyleAttrEntry.map.value);
if (kDebugStyles) {
ALOGI(
"-> From def style: type=0x%x, data=0x%08x",
value.get().dataType, value.get().data);
}
}
}
Ref resid = new Ref<>(0);
if (value.get().dataType != DataType.NULL.code()) {
// Take care of resolving the found resource to its final value.
int newBlock = theme.resolveAttributeReference(value, block, resid, typeSetFlags, config);
if (newBlock >= 0) {
block = newBlock;
}
if (kDebugStyles) {
ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
}
} else if (value.get().data != Res_value.DATA_NULL_EMPTY) {
// If we still don't have a value for this attribute, try to find it in the theme!
int newBlock = theme.GetAttribute(curIdent, value, typeSetFlags);
if (newBlock >= 0) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.get().dataType, value.get().data);
}
newBlock = res.resolveReference(value, newBlock, resid, typeSetFlags, config);
if (newBlock >= 0) {
block = newBlock;
}
if (kDebugStyles) {
ALOGI(
"-> Resolved theme: type=0x%x, data=0x%08x",
value.get().dataType, value.get().data);
}
}
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.get().dataType == DataType.REFERENCE.code() && value.get().data == 0) {
if (kDebugStyles) {
ALOGI(". Setting to @null!");
}
value.set(Res_value.NULL_VALUE);
block = kXmlBlock;
}
if (kDebugStyles) {
ALOGI(
"Attribute 0x%08x: type=0x%x, data=0x%08x",
curIdent, value.get().dataType, value.get().data);
}
// Write the final value back to Java.
int destIndex = ii * STYLE_NUM_ENTRIES;
Res_value res_value = value.get();
outValues[destIndex + STYLE_TYPE] = res_value.dataType;
outValues[destIndex + STYLE_DATA] = res_value.data;
outValues[destIndex + STYLE_ASSET_COOKIE] =
block != kXmlBlock ? res.getTableCookie(block) : -1;
outValues[destIndex + STYLE_RESOURCE_ID] = resid.get();
outValues[destIndex + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
outValues[destIndex + STYLE_DENSITY] = config.get().density;
if (res_value.dataType != DataType.NULL.code()
|| res_value.data == Res_value.DATA_NULL_EMPTY) {
indices_idx++;
// out_indices must NOT be nullptr.
outIndices[indices_idx] = ii;
}
if (res_value.dataType == DataType.ATTRIBUTE.code()) {
ResTable.ResourceName attrName = new ResTable.ResourceName();
ResTable.ResourceName attrRefName = new ResTable.ResourceName();
boolean gotName = res.getResourceName(curIdent, true, attrName);
boolean gotRefName = res.getResourceName(res_value.data, true, attrRefName);
Logger.warn(
"Failed to resolve attribute lookup: %s=\"?%s\"; theme: %s",
gotName ? attrName : "unknown", gotRefName ? attrRefName : "unknown", theme);
}
// out_values += STYLE_NUM_ENTRIES;
}
res.unlock();
// out_indices must NOT be nullptr.
outIndices[0] = indices_idx;
}
public static boolean RetrieveAttributes(
ResTable res,
ResXMLParser xmlParser,
int[] attrs,
int attrsLength,
int[] outValues,
int[] outIndices) {
Ref config = new Ref<>(new ResTable_config());
Ref value = new Ref<>(null);
int indices_idx = 0;
// Now lock down the resource object and start pulling stuff from it.
res.lock();
// Retrieve the XML attributes, if requested.
final int xmlAttrCount = xmlParser.getAttributeCount();
int ix = 0;
int curXmlAttr = xmlParser.getAttributeNameResID(ix);
final int kXmlBlock = 0x10000000;
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
int baseDest = 0;
for (int ii = 0; ii < attrsLength; ii++) {
final int curIdent = attrs[ii];
int block = 0;
Ref typeSetFlags = new Ref<>(0);
value.set(Res_value.NULL_VALUE);
config.get().density = 0;
// Try to find a value for this attribute...
// Skip through XML attributes until the end or the next possible match.
while (ix < xmlAttrCount && curIdent > curXmlAttr) {
ix++;
curXmlAttr = xmlParser.getAttributeNameResID(ix);
}
// Retrieve the current XML attribute if it matches, and step to next.
if (ix < xmlAttrCount && curIdent == curXmlAttr) {
block = kXmlBlock;
xmlParser.getAttributeValue(ix, value);
ix++;
curXmlAttr = xmlParser.getAttributeNameResID(ix);
}
// printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
Ref resid = new Ref<>(0);
if (value.get().dataType != Res_value.TYPE_NULL) {
// Take care of resolving the found resource to its final value.
// printf("Resolving attribute reference\n");
int newBlock = res.resolveReference(value, block, resid, typeSetFlags, config);
if (newBlock >= 0) block = newBlock;
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.get().dataType == Res_value.TYPE_REFERENCE && value.get().data == 0) {
value.set(Res_value.NULL_VALUE);
block = kXmlBlock;
}
// printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType,
// value.data);
// Write the final value back to Java.
outValues[baseDest + STYLE_TYPE] = value.get().dataType;
outValues[baseDest + STYLE_DATA] = value.get().data;
outValues[baseDest + STYLE_ASSET_COOKIE] =
block != kXmlBlock ? res.getTableCookie(block) : -1;
outValues[baseDest + STYLE_RESOURCE_ID] = resid.get();
outValues[baseDest + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get();
outValues[baseDest + STYLE_DENSITY] = config.get().density;
if (outIndices != null
&& (value.get().dataType != Res_value.TYPE_NULL
|| value.get().data == Res_value.DATA_NULL_EMPTY)) {
indices_idx++;
outIndices[indices_idx] = ii;
}
// dest += STYLE_NUM_ENTRIES;
baseDest += STYLE_NUM_ENTRIES;
}
res.unlock();
if (outIndices != null) {
outIndices[0] = indices_idx;
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy