blob: 7cde91296ed602af2fead8cb2740d4c8a50f6485 [file] [log] [blame]
package org.robolectric.shadows;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.ParcelFileDescriptor;
import android.util.AttributeSet;
import android.util.TypedValue;
import org.jetbrains.annotations.NotNull;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.res.*;
import org.robolectric.res.builder.XmlBlock;
import org.robolectric.res.builder.XmlResourceParserImpl;
import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.RuntimeEnvironment.castNativePtr;
import static org.robolectric.Shadows.shadowOf;
/**
* Shadow for {@link android.content.res.AssetManager}.
*/
@Implements(AssetManager.class)
public final class ShadowAssetManager {
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;
boolean strictErrors = false;
private static long nextInternalThemeId = 1000;
private static final Map<Long, NativeTheme> nativeThemes = new HashMap<>();
private ResourceTable resourceTable;
class NativeTheme {
private ThemeStyleSet themeStyleSet;
public NativeTheme(ThemeStyleSet themeStyleSet) {
this.themeStyleSet = themeStyleSet;
}
public ShadowAssetManager getShadowAssetManager() {
return ShadowAssetManager.this;
}
}
@RealObject
AssetManager realObject;
private void convertAndFill(AttributeResource attribute, TypedValue outValue, String qualifiers, boolean resolveRefs) {
if (attribute.isNull()) {
outValue.type = TypedValue.TYPE_NULL;
outValue.data = TypedValue.DATA_NULL_UNDEFINED;
return;
} else if (attribute.isEmpty()) {
outValue.type = TypedValue.TYPE_NULL;
outValue.data = TypedValue.DATA_NULL_EMPTY;
return;
}
// short-circuit Android caching of loaded resources cuz our string positions don't remain stable...
outValue.assetCookie = Converter.getNextStringCookie();
// TODO: Handle resource and style references
if (attribute.isStyleReference()) {
return;
}
while (attribute.isResourceReference()) {
Integer resourceId;
ResName resName = attribute.getResourceReference();
if (attribute.getReferenceResId() != null) {
resourceId = attribute.getReferenceResId();
} else {
resourceId = resourceTable.getResourceId(resName);
}
if (resourceId == null) {
throw new Resources.NotFoundException("unknown resource " + resName);
}
outValue.type = TypedValue.TYPE_REFERENCE;
if (!resolveRefs) {
// Just return the resourceId if resolveRefs is false.
outValue.data = resourceId;
return;
}
outValue.resourceId = resourceId;
TypedResource dereferencedRef = resourceTable.getValue(resName, qualifiers);
if (dereferencedRef == null) {
Logger.strict("couldn't resolve %s from %s", resName.getFullyQualifiedName(), attribute);
if (resName.type.equals("id")) {
return;
} else if (resName.type.equals("layout")) {
return; // resourceId is good enough, right?
} else if (resName.type.equals("dimen")) {
return;
} else if (resName.type.equals("transition")) {
return;
} else if (resName.type.equals("interpolator")) {
return;
} else if (resName.type.equals("menu")) {
return;
} else if (resName.type.equals("raw")) {
return;
} else if (DrawableResourceLoader.isStillHandledHere(resName.type)) {
// wtf. color and drawable references reference are all kinds of stupid.
TypedResource drawableResource = resourceTable.getValue(resName, qualifiers);
if (drawableResource == null) {
throw new Resources.NotFoundException("can't find file for " + resName);
} else {
outValue.type = TypedValue.TYPE_STRING;
outValue.data = 0;
outValue.assetCookie = Converter.getNextStringCookie();
outValue.string = (CharSequence) drawableResource.getData();
return;
}
} else {
throw new RuntimeException("huh? " + resName);
}
} else {
if (dereferencedRef.isFile()) {
outValue.type = TypedValue.TYPE_STRING;
outValue.data = 0;
outValue.assetCookie = Converter.getNextStringCookie();
outValue.string = dereferencedRef.asString();
return;
} else if (dereferencedRef.getData() instanceof String) {
attribute = new AttributeResource(attribute.resName, dereferencedRef.asString(), resName.packageName);
if (attribute.isResourceReference()) {
continue;
}
if (resolveRefs) {
Converter.getConverter(dereferencedRef.getResType()).fillTypedValue(attribute.value, outValue);
return;
}
}
}
break;
}
if (attribute.isNull()) {
outValue.type = TypedValue.TYPE_NULL;
return;
}
TypedResource attrTypeData = resourceTable.getValue(attribute.resName, qualifiers);
if (attrTypeData != null) {
AttrData attrData = (AttrData) attrTypeData.getData();
String format = attrData.getFormat();
String[] types = format.split("\\|");
for (String type : types) {
if ("reference".equals(type)) continue; // already handled above
Converter converter = Converter.getConverterFor(attrData, type);
if (converter != null) {
if (converter.fillTypedValue(attribute.value, outValue)) {
return;
}
}
}
} else {
/**
* In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a
* KitKat runtine, then infer the attribute type from the value.
*
* TODO: When we are able to pass the SDK resources from the build environment then we can remove this
* and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information.
*/
ResType resType = ResType.inferFromValue(attribute.value);
Converter.getConverter(resType).fillTypedValue(attribute.value, outValue);
}
}
public void __constructor__() {
resourceTable = RuntimeEnvironment.getAppResourceProvider();
}
public void __constructor__(boolean isSystem) {
resourceTable = isSystem ? RuntimeEnvironment.getSystemResourceProvider() : RuntimeEnvironment.getAppResourceProvider();
}
public ResourceTable getResourceTable() {
return resourceTable;
}
@HiddenApi @Implementation
public CharSequence getResourceText(int ident) {
TypedResource value = getAndResolve(ident, RuntimeEnvironment.getQualifiers(), true);
if (value == null) return null;
return (CharSequence) value.getData();
}
@HiddenApi @Implementation
public CharSequence getResourceBagText(int ident, int bagEntryId) {
throw new UnsupportedOperationException(); // todo
}
@HiddenApi @Implementation
public String[] getResourceStringArray(final int id) {
CharSequence[] resourceTextArray = getResourceTextArray(id);
if (resourceTextArray == null) return null;
String[] strings = new String[resourceTextArray.length];
for (int i = 0; i < strings.length; i++) {
strings[i] = resourceTextArray[i].toString();
}
return strings;
}
@HiddenApi @Implementation
public int getResourceIdentifier(String name, String defType, String defPackage) {
ResName resName = ResName.qualifyResName(name, defPackage, defType);
// If the resource does not exist then return 0, otherwise ResourceIndex.getResourceId() will generate a placeholder.
if (!ResName.ID_TYPE.equals(resName.type)
&& !resourceTable.hasValue(resName, RuntimeEnvironment.getQualifiers())) {
return 0;
}
Integer resourceId = resourceTable.getResourceId(resName);
return resourceId == null ? 0 : resourceId;
}
@HiddenApi @Implementation
public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) {
TypedResource value = getAndResolve(ident, RuntimeEnvironment.getQualifiers(), resolveRefs);
if (value == null) return false;
getConverter(value).fillTypedValue(value.getData(), outValue);
return true;
}
private Converter getConverter(TypedResource value) {
if (value instanceof FileTypedResource.Image
|| (value instanceof FileTypedResource
&& ((FileTypedResource) value).getFsFile().getName().endsWith(".xml"))) {
return new Converter.FromFilePath();
}
return Converter.getConverter(value.getResType());
}
@HiddenApi @Implementation
public CharSequence[] getResourceTextArray(int resId) {
TypedResource value = getAndResolve(resId, RuntimeEnvironment.getQualifiers(), true);
if (value == null) return null;
TypedResource[] items = getConverter(value).getItems(value);
CharSequence[] charSequences = new CharSequence[items.length];
for (int i = 0; i < items.length; i++) {
TypedResource typedResource = resolve(items[i], RuntimeEnvironment.getQualifiers(), resId);
charSequences[i] = getConverter(typedResource).asCharSequence(typedResource);
}
return charSequences;
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public boolean getThemeValue(int themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
return getThemeValue((long) themePtr, ident, outValue, resolveRefs);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) {
ResName resName = resourceTable.getResName(ident);
ThemeStyleSet themeStyleSet = getNativeTheme(themePtr).themeStyleSet;
AttributeResource attrValue = themeStyleSet.getAttrValue(resName);
while(attrValue != null && attrValue.isStyleReference()) {
ResName attrResName = attrValue.getStyleReference();
if (attrValue.resName.equals(attrResName)) {
Logger.info("huh... circular reference for %s?", attrResName.getFullyQualifiedName());
return false;
}
attrValue = themeStyleSet.getAttrValue(attrResName);
}
if (attrValue != null) {
convertAndFill(attrValue, outValue, RuntimeEnvironment.getQualifiers(), resolveRefs);
return true;
}
return false;
}
@HiddenApi @Implementation
public void ensureStringBlocks() {
}
@Implementation
public final InputStream open(String fileName) throws IOException {
return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream();
}
@Implementation
public final InputStream open(String fileName, int accessMode) throws IOException {
return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream();
}
@Implementation
public final AssetFileDescriptor openFd(String fileName) throws IOException {
File file = new File(ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getPath());
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length());
}
@Implementation
public final String[] list(String path) throws IOException {
FsFile file = ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(path);
if (file.isDirectory()) {
return file.listFileNames();
}
return new String[0];
}
@HiddenApi @Implementation
public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException {
final ResName resName = qualifyFromNonAssetFileName(fileName);
final FileTypedResource typedResource =
(FileTypedResource) resourceTable.getValue(resName, RuntimeEnvironment.getQualifiers());
if (typedResource == null) {
throw new IOException("Unable to find resource for " + fileName);
}
if (accessMode == AssetManager.ACCESS_STREAMING) {
return typedResource.getFsFile().getInputStream();
} else {
return new ByteArrayInputStream(typedResource.getFsFile().getBytes());
}
}
private ResName qualifyFromNonAssetFileName(String fileName) {
if (fileName.startsWith("jar:")) {
// Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows
return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", ""));
} else {
return ResName.qualifyFromFilePath(ShadowApplication.getInstance().getAppManifest().getPackageName(), fileName);
}
}
@HiddenApi @Implementation
public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException {
throw new UnsupportedOperationException();
}
@Implementation
public final XmlResourceParser openXmlResourceParser(int cookie, String fileName) throws IOException {
return getXmlResourceParser(null, XmlBlock.create(fileName, "fixme"), "fixme");
}
public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
ResName resName = getResName(resId);
ResName resolvedResName = resolveResName(resName, RuntimeEnvironment.getQualifiers());
if (resolvedResName == null) {
throw new RuntimeException("couldn't resolve " + resName.getFullyQualifiedName());
}
resName = resolvedResName;
XmlBlock block = resourceTable.getXml(resName, RuntimeEnvironment.getQualifiers());
if (block == null) {
throw new Resources.NotFoundException(resName.getFullyQualifiedName());
}
ResourceTable resourceProvider = ResourceIds.isFrameworkResource(resId) ? RuntimeEnvironment.getSystemResourceProvider() : RuntimeEnvironment.getCompiletimeResourceProvider();
return getXmlResourceParser(resourceProvider, block, resName.packageName);
}
private XmlResourceParser getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName) {
return new XmlResourceParserImpl(block.getDocument(), block.getFilename(), block.getPackageName(),
packageName, resourceProvider);
}
@HiddenApi @Implementation
public int addAssetPath(String path) {
return 1;
}
@HiddenApi @Implementation
public boolean isUpToDate() {
return true;
}
@HiddenApi @Implementation
public void setLocale(String locale) {
}
@Implementation
public String[] getLocales() {
return new String[0]; // todo
}
@HiddenApi @Implementation
public void setConfiguration(int mcc, int mnc, String locale,
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
int screenLayout, int uiMode, int majorVersion) {
}
@HiddenApi @Implementation
public int[] getArrayIntResource(int resId) {
TypedResource value = getAndResolve(resId, RuntimeEnvironment.getQualifiers(), true);
if (value == null) return null;
TypedResource[] items = getConverter(value).getItems(value);
int[] ints = new int[items.length];
for (int i = 0; i < items.length; i++) {
TypedResource typedResource = resolve(items[i], RuntimeEnvironment.getQualifiers(), resId);
ints[i] = getConverter(typedResource).asInt(typedResource);
}
return ints;
}
@HiddenApi @Implementation
public Number createTheme() {
synchronized (nativeThemes) {
long nativePtr = nextInternalThemeId++;
nativeThemes.put(nativePtr, new NativeTheme(new ThemeStyleSet()));
return castNativePtr(nativePtr);
}
}
private static NativeTheme getNativeTheme(Resources.Theme theme) {
return getNativeTheme(shadowOf(theme).getNativePtr());
}
private static NativeTheme getNativeTheme(long themePtr) {
NativeTheme nativeTheme;
synchronized (nativeThemes) {
nativeTheme = nativeThemes.get(themePtr);
}
if (nativeTheme == null) {
throw new RuntimeException("no theme " + themePtr + " found in AssetManager");
}
return nativeTheme;
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public void releaseTheme(int themePtr) {
releaseTheme((long) themePtr);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public void releaseTheme(long themePtr) {
synchronized (nativeThemes) {
nativeThemes.remove(themePtr);
}
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public static void applyThemeStyle(int themePtr, int styleRes, boolean force) {
applyThemeStyle((long) themePtr, styleRes, force);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public static void applyThemeStyle(long themePtr, int styleRes, boolean force) {
NativeTheme nativeTheme = getNativeTheme(themePtr);
Style style = nativeTheme.getShadowAssetManager().resolveStyle(styleRes, null);
nativeTheme.themeStyleSet.apply(style, force);
}
@HiddenApi @Implementation(maxSdk = KITKAT_WATCH)
public static void copyTheme(int destPtr, int sourcePtr) {
copyTheme((long) destPtr, (long) sourcePtr);
}
@HiddenApi @Implementation(minSdk = LOLLIPOP)
public static void copyTheme(long destPtr, long sourcePtr) {
NativeTheme destNativeTheme = getNativeTheme(destPtr);
NativeTheme sourceNativeTheme = getNativeTheme(sourcePtr);
destNativeTheme.themeStyleSet = sourceNativeTheme.themeStyleSet.copy();
}
/////////////////////////
Style resolveStyle(int resId, Style themeStyleSet) {
return resolveStyle(getResName(resId), themeStyleSet);
}
private Style resolveStyle(@NotNull ResName themeStyleName, Style themeStyleSet) {
TypedResource themeStyleResource = resourceTable.getValue(themeStyleName, RuntimeEnvironment.getQualifiers());
if (themeStyleResource == null) return null;
StyleData themeStyleData = (StyleData) themeStyleResource.getData();
if (themeStyleSet == null) {
themeStyleSet = new ThemeStyleSet();
}
return new StyleResolver(resourceTable, shadowOf(AssetManager.getSystem()).getResourceTable(), themeStyleData, themeStyleSet, themeStyleName, RuntimeEnvironment.getQualifiers());
}
private TypedResource getAndResolve(int resId, String qualifiers, boolean resolveRefs) {
TypedResource value = resourceTable.getValue(resId, qualifiers);
if (resolveRefs) {
value = resolve(value, qualifiers, resId);
}
// todo: make the drawable loader put stuff into the normal spot...
String resourceTypeName = getResourceTypeName(resId);
if (value == null && DrawableResourceLoader.isStillHandledHere(resourceTypeName)) {
FileTypedResource typedResource = (FileTypedResource) resourceTable.getValue(resId, qualifiers);
return new TypedResource<>(typedResource.getFsFile(), ResType.FILE, typedResource.getXmlContext());
}
// todo: gross. this is so resources.getString(R.layout.foo) works for ABS.
if (value == null && "layout".equals(resourceTypeName)) {
throw new UnsupportedOperationException("ugh, this doesn't work still?");
}
return value;
}
TypedResource resolve(TypedResource value, String qualifiers, int resId) {
return resolveResourceValue(value, qualifiers, resId);
}
public ResName resolveResName(ResName resName, String qualifiers) {
TypedResource value = resourceTable.getValue(resName, qualifiers);
return resolveResource(value, qualifiers, resName);
}
// todo: DRY up #resolveResource vs #resolveResourceValue
private ResName resolveResource(TypedResource value, String qualifiers, ResName resName) {
while (value != null && value.isReference()) {
String s = value.asString();
if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) {
value = null;
} else {
String refStr = s.substring(1).replace("+", "");
resName = ResName.qualifyResName(refStr, resName);
value = resourceTable.getValue(resName, qualifiers);
}
}
return resName;
}
private TypedResource resolveResourceValue(TypedResource value, String qualifiers, ResName resName) {
while (value != null && value.isReference()) {
String s = value.asString();
if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) {
value = null;
} else {
String refStr = s.substring(1).replace("+", "");
resName = ResName.qualifyResName(refStr, resName);
value = resourceTable.getValue(resName, qualifiers);
}
}
return value;
}
public TypedResource resolveResourceValue(TypedResource value, String qualifiers, int resId) {
ResName resName = getResName(resId);
return resolveResourceValue(value, qualifiers, resName);
}
private TypedValue buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes) {
/*
* When determining the final value of a particular attribute, there are four inputs that come into play:
*
* 1. Any attribute values in the given AttributeSet.
* 2. The style resource specified in the AttributeSet (named "style").
* 3. The default style specified by defStyleAttr and defStyleRes
* 4. The base values in this theme.
*/
Style defStyleFromAttr = null;
Style defStyleFromRes = null;
Style styleAttrStyle = null;
if (defStyleAttr != 0) {
// Load the theme attribute for the default style attributes. E.g., attr/buttonStyle
ResName defStyleName = getResName(defStyleAttr);
// Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button";
AttributeResource defStyleAttribute = themeStyleSet.getAttrValue(defStyleName);
if (defStyleAttribute != null) {
while (defStyleAttribute.isStyleReference()) {
AttributeResource other = themeStyleSet.getAttrValue(defStyleAttribute.getStyleReference());
if (other == null) {
throw new RuntimeException("couldn't dereference " + defStyleAttribute);
}
defStyleAttribute = other;
}
if (defStyleAttribute.isResourceReference()) {
ResName defStyleResName = defStyleAttribute.getResourceReference();
defStyleFromAttr = resolveStyle(defStyleResName, themeStyleSet);
}
}
}
if (set != null && set.getStyleAttribute() != 0) {
ResName styleAttributeResName = getResName(set.getStyleAttribute());
while (styleAttributeResName.type.equals("attr")) {
AttributeResource attrValue = themeStyleSet.getAttrValue(styleAttributeResName);
if (attrValue == null) {
throw new RuntimeException(
"no value for " + styleAttributeResName.getFullyQualifiedName()
+ " in " + themeStyleSet);
}
if (attrValue.isResourceReference()) {
styleAttributeResName = attrValue.getResourceReference();
} else if (attrValue.isStyleReference()) {
styleAttributeResName = attrValue.getStyleReference();
}
}
styleAttrStyle = resolveStyle(styleAttributeResName, themeStyleSet);
}
if (defStyleRes != 0) {
ResName resName = getResName(defStyleRes);
if (resName.type.equals("attr")) {
AttributeResource attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, themeStyleSet);
if (attributeValue != null) {
if (attributeValue.isStyleReference()) {
resName = themeStyleSet.getAttrValue(attributeValue.getStyleReference()).getResourceReference();
} else if (attributeValue.isResourceReference()) {
resName = attributeValue.getResourceReference();
}
}
}
defStyleFromRes = resolveStyle(resName, themeStyleSet);
}
AttributeResource attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, themeStyleSet);
while (attribute != null && attribute.isStyleReference()) {
ResName otherAttrName = attribute.getStyleReference();
if (attribute.resName.equals(otherAttrName)) {
Logger.info("huh... circular reference for %s?", attribute.resName.getFullyQualifiedName());
return null;
}
ResName resName = resourceTable.getResName(resId);
AttributeResource otherAttr = themeStyleSet.getAttrValue(otherAttrName);
if (otherAttr == null) {
strictError("no such attr %s in %s while resolving value for %s", attribute.value, themeStyleSet, resName.getFullyQualifiedName());
attribute = null;
} else {
attribute = new AttributeResource(resName, otherAttr.value, otherAttr.contextPackageName);
}
}
if (attribute == null || attribute.isNull()) {
return null;
} else {
TypedValue typedValue = new TypedValue();
convertAndFill(attribute, typedValue, RuntimeEnvironment.getQualifiers(), true);
return typedValue;
}
}
private void strictError(String message, Object... args) {
if (strictErrors) {
throw new RuntimeException(String.format(message, args));
} else {
Logger.strict(message, args);
}
}
TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes) {
CharSequence[] stringData = new CharSequence[attrs.length];
int[] data = new int[attrs.length * ShadowAssetManager.STYLE_NUM_ENTRIES];
int[] indices = new int[attrs.length + 1];
int nextIndex = 0;
Style themeStyleSet = nativeTheme == 0
? new EmptyStyle()
: getNativeTheme(nativeTheme).themeStyleSet;
for (int i = 0; i < attrs.length; i++) {
int offset = i * ShadowAssetManager.STYLE_NUM_ENTRIES;
TypedValue typedValue = buildTypedValue(set, attrs[i], defStyleAttr, themeStyleSet, defStyleRes);
if (typedValue != null) {
//noinspection PointlessArithmeticExpression
data[offset + ShadowAssetManager.STYLE_TYPE] = typedValue.type;
data[offset + ShadowAssetManager.STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data;
data[offset + ShadowAssetManager.STYLE_ASSET_COOKIE] = typedValue.assetCookie;
data[offset + ShadowAssetManager.STYLE_RESOURCE_ID] = typedValue.resourceId;
data[offset + ShadowAssetManager.STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations;
data[offset + ShadowAssetManager.STYLE_DENSITY] = typedValue.density;
stringData[i] = typedValue.string;
indices[nextIndex + 1] = i;
nextIndex++;
}
}
indices[0] = nextIndex;
TypedArray typedArray = ShadowTypedArray.create(resources, attrs, data, indices, nextIndex, stringData);
if (set != null) {
shadowOf(typedArray).positionDescription = set.getPositionDescription();
}
return typedArray;
}
private AttributeResource findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @NotNull Style themeStyleSet) {
if (attributeSet != null) {
for (int i = 0; i < attributeSet.getAttributeCount(); i++) {
if (attributeSet.getAttributeNameResource(i) == resId && attributeSet.getAttributeValue(i) != null) {
String defaultPackageName = ResourceIds.isFrameworkResource(resId) ? "android" : RuntimeEnvironment.application.getPackageName();
ResName resName = ResName.qualifyResName(attributeSet.getAttributeName(i), defaultPackageName, "attr");
Integer referenceResId = null;
if (AttributeResource.isResourceReference(attributeSet.getAttributeValue(i))) {
referenceResId = attributeSet.getAttributeResourceValue(i, -1);
}
return new AttributeResource(resName, attributeSet.getAttributeValue(i), "fixme!!!", referenceResId);
}
}
}
ResName attrName = resourceTable.getResName(resId);
if (attrName == null) return null;
if (styleAttrStyle != null) {
AttributeResource attribute = styleAttrStyle.getAttrValue(attrName);
if (attribute != null) {
return attribute;
}
}
// else if attr in defStyleFromAttr, use its value
if (defStyleFromAttr != null) {
AttributeResource attribute = defStyleFromAttr.getAttrValue(attrName);
if (attribute != null) {
return attribute;
}
}
if (defStyleFromRes != null) {
AttributeResource attribute = defStyleFromRes.getAttrValue(attrName);
if (attribute != null) {
return attribute;
}
}
// else if attr in theme, use its value
return themeStyleSet.getAttrValue(attrName);
}
@NotNull private ResName getResName(int id) {
ResName resName = resourceTable.getResName(id);
if (resName == null) {
throw new Resources.NotFoundException("Unable to find resource ID #0x" + Integer.toHexString(id)
+ " in packages " + resourceTable);
}
return resName;
}
@Implementation
public String getResourceName(int resid) {
return getResName(resid).getFullyQualifiedName();
}
@Implementation
public String getResourcePackageName(int resid) {
return getResName(resid).packageName;
}
@Implementation
public String getResourceTypeName(int resid) {
return getResName(resid).type;
}
@Implementation
public String getResourceEntryName(int resid) {
return getResName(resid).name;
}
@Resetter
public static void reset() {
ReflectionHelpers.setStaticField(AssetManager.class, "sSystem", null);
}
}