Skip to content

Commit

Permalink
Merge pull request #2840 from robolectric/christinef-typed-arrays
Browse files Browse the repository at this point in the history
Add support for TypedArrays [xian]
  • Loading branch information
xian committed Jan 11, 2017
2 parents 7d88405 + d79e6a7 commit fbf415e
Show file tree
Hide file tree
Showing 17 changed files with 347 additions and 31 deletions.
Expand Up @@ -38,7 +38,15 @@ public TypedResource getValueWithType(XpathResourceXmlLoader.XmlNode xmlNode, Xm
@Override public TypedResource getValueWithType(XpathResourceXmlLoader.XmlNode xmlNode, XmlLoader.XmlContext xmlContext) {
return extractScalarItems(xmlNode, INTEGER_ARRAY, INTEGER, xmlContext);
}
};
},

TYPED_ARRAY {
@Override public TypedResource getValueWithType(XpathResourceXmlLoader.XmlNode xmlNode, XmlLoader.XmlContext xmlContext) {
return extractTypedItems(xmlNode, TYPED_ARRAY, xmlContext);
}
},

NULL;

private static TypedResource extractScalarItems(XpathResourceXmlLoader.XmlNode xmlNode, ResType arrayResType, ResType itemResType, XmlLoader.XmlContext xmlContext) {
List<TypedResource> items = new ArrayList<>();
Expand All @@ -49,6 +57,27 @@ private static TypedResource extractScalarItems(XpathResourceXmlLoader.XmlNode x
return new TypedResource<>(typedResources, arrayResType, xmlContext);
}

private static TypedResource extractTypedItems(XpathResourceXmlLoader.XmlNode xmlNode, ResType arrayResType, XmlLoader.XmlContext xmlContext) {
final List<TypedResource> items = new ArrayList<>();
for (XpathResourceXmlLoader.XmlNode item : xmlNode.selectElements("item")) {
final String itemString = item.getTextContent();
ResType itemResType = inferFromValue(itemString);
if (itemResType == ResType.CHAR_SEQUENCE) {
if (AttributeResource.isStyleReference(itemString)) {
itemResType = ResType.STYLE;
} else if (itemString.equals("@null")) {
itemResType = ResType.NULL;
} else if (AttributeResource.isResourceReference(itemString)) {
// This is a reference; no type info needed.
itemResType = null;
}
}
items.add(new TypedResource<>(itemString, itemResType, xmlContext));
}
final TypedResource[] typedResources = items.toArray(new TypedResource[items.size()]);
return new TypedResource<>(typedResources, arrayResType, xmlContext);
}

public TypedResource getValueWithType(XpathResourceXmlLoader.XmlNode xmlNode, XmlLoader.XmlContext xmlContext) {
return new TypedResource<>(xmlNode.getTextContent(), this, xmlContext);
}
Expand All @@ -61,7 +90,7 @@ public static ResType inferFromValue(String value) {
return ResType.COLOR;
} else if ("true".equals(value) || "false".equals(value)) {
return ResType.BOOLEAN;
} else if (value.endsWith("dp") || value.endsWith("sp") || value.endsWith("pt") || value.endsWith("px") || value.endsWith("mm") || value.endsWith("in")) {
} else if (value.endsWith("dp") || value.endsWith("sp") || value.endsWith("pt") || value.endsWith("px") || value.endsWith("mm") || value.endsWith("in") || value.endsWith("dip")) {
return ResType.DIMEN;
} else {
try {
Expand Down
Expand Up @@ -37,6 +37,9 @@ static void load(String packageName, ResourcePath resourcePath, PackageResourceT
new ValueResourceLoader(data, "/resources/string", "string", ResType.CHAR_SEQUENCE),
new ValueResourceLoader(data, "/resources/item[@type='string']", "string", ResType.CHAR_SEQUENCE),
new ValueResourceLoader(data, "/resources/string-array", "array", ResType.CHAR_SEQUENCE_ARRAY),
new ValueResourceLoader(data, "/resources/array", "array", ResType.TYPED_ARRAY),
new ValueResourceLoader(data, "/resources/id", "id", ResType.CHAR_SEQUENCE),
new ValueResourceLoader(data, "/resources/item[@type='id']", "id", ResType.CHAR_SEQUENCE),
new AttrResourceLoader(data),
new StyleResourceLoader(data)
);
Expand Down
Expand Up @@ -32,7 +32,8 @@ public XmlLoader.XmlContext getXmlContext() {
}

public String asString() {
return ((String) getData());
T data = getData();
return data instanceof String ? (String) data : null;
}

public boolean isFile() {
Expand Down
@@ -1,10 +1,10 @@
package org.robolectric.shadows;

import android.content.res.Resources;
import android.util.TypedValue;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.res.*;
import org.robolectric.util.Logger;
import org.robolectric.res.AttrData;
import org.robolectric.res.FsFile;
import org.robolectric.res.ResType;
import org.robolectric.res.TypedResource;
import org.robolectric.util.Util;

public class Converter<T> {
Expand Down Expand Up @@ -66,14 +66,17 @@ public static Converter getConverter(ResType resType) {
return new FromFraction();
case CHAR_SEQUENCE_ARRAY:
case INTEGER_ARRAY:
case TYPED_ARRAY:
return new FromArray();
case STYLE:
return new Converter();
default:
throw new UnsupportedOperationException(resType.name());
throw new UnsupportedOperationException("can't convert from " + resType.name());
}
}

public CharSequence asCharSequence(TypedResource typedResource) {
throw cantDo("asCharSequence");
return typedResource.asString();
}

public int asInt(TypedResource typedResource) {
Expand Down
Expand Up @@ -15,7 +15,21 @@
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.res.*;
import org.robolectric.res.AttrData;
import org.robolectric.res.AttributeResource;
import org.robolectric.res.DrawableResourceLoader;
import org.robolectric.res.EmptyStyle;
import org.robolectric.res.FileTypedResource;
import org.robolectric.res.FsFile;
import org.robolectric.res.ResName;
import org.robolectric.res.ResType;
import org.robolectric.res.ResourceIds;
import org.robolectric.res.ResourceTable;
import org.robolectric.res.Style;
import org.robolectric.res.StyleData;
import org.robolectric.res.StyleResolver;
import org.robolectric.res.ThemeStyleSet;
import org.robolectric.res.TypedResource;
import org.robolectric.res.builder.XmlBlock;
import org.robolectric.res.builder.XmlResourceParserImpl;
import org.robolectric.util.Logger;
Expand Down Expand Up @@ -431,6 +445,109 @@ public int[] getArrayIntResource(int resId) {
return ints;
}

protected TypedArray getTypedArrayResource(Resources resources, int resId) {
TypedResource value = getAndResolve(resId, RuntimeEnvironment.getQualifiers(), true);
if (value == null) {
return null;
}
TypedResource[] items = getConverter(value).getItems(value);
return getTypedArray(resources, items, resId);
}

private TypedArray getTypedArray(Resources resources, TypedResource[] typedResources, int resId) {
final CharSequence[] stringData = new CharSequence[typedResources.length];
final int totalLen = typedResources.length * ShadowAssetManager.STYLE_NUM_ENTRIES;
final int[] data = new int[totalLen];

for (int i = 0; i < typedResources.length; i++) {
final int offset = i * ShadowAssetManager.STYLE_NUM_ENTRIES;
TypedResource typedResource = typedResources[i];

// Classify the item.
int type = getResourceType(typedResource);
if (type == -1) {
// This type is unsupported; leave empty.
continue;
}

final TypedValue typedValue = new TypedValue();

if (type == TypedValue.TYPE_REFERENCE) {
final String reference = typedResource.asString();
ResName refResName = AttributeResource.getResourceReference(reference,
typedResource.getXmlContext().getPackageName(), null);
typedValue.resourceId = resourceTable.getResourceId(refResName);
typedValue.data = typedValue.resourceId;
typedResource = resolve(typedResource, RuntimeEnvironment.getQualifiers(), typedValue.resourceId);

if (typedResource != null) {
// Reclassify to a non-reference type.
type = getResourceType(typedResource);
if (type == TypedValue.TYPE_ATTRIBUTE) {
type = TypedValue.TYPE_REFERENCE;
} else if (type == -1) {
// This type is unsupported; leave empty.
continue;
}
}
}

if (type == TypedValue.TYPE_ATTRIBUTE) {
final String reference = typedResource.asString();
final ResName attrResName = AttributeResource.getStyleReference(reference,
typedResource.getXmlContext().getPackageName(), "attr");
typedValue.data = resourceTable.getResourceId(attrResName);
}

if (typedResource != null && type != TypedValue.TYPE_NULL && type != TypedValue.TYPE_ATTRIBUTE) {
getConverter(typedResource).fillTypedValue(typedResource.getData(), typedValue);
}

data[offset + ShadowAssetManager.STYLE_TYPE] = type;
data[offset + ShadowAssetManager.STYLE_RESOURCE_ID] = typedValue.resourceId;
data[offset + ShadowAssetManager.STYLE_DATA] = typedValue.data;
data[offset + ShadowAssetManager.STYLE_ASSET_COOKIE] = typedValue.assetCookie;
data[offset + ShadowAssetManager.STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations;
data[offset + ShadowAssetManager.STYLE_DENSITY] = typedValue.density;
stringData[i] = typedResource == null ? null : typedResource.asString();
}

int[] indices = new int[typedResources.length + 1]; /* keep zeroed out */
return ShadowTypedArray.create(resources, null, data, indices, typedResources.length, stringData);
}

private int getResourceType(TypedResource typedResource) {
if (typedResource == null) {
return -1;
}
final ResType resType = typedResource.getResType();
int type;
if (typedResource.getData() == null || resType == ResType.NULL) {
type = TypedValue.TYPE_NULL;
} else if (typedResource.isReference()) {
type = TypedValue.TYPE_REFERENCE;
} else if (resType == ResType.STYLE) {
type = TypedValue.TYPE_ATTRIBUTE;
} else if (resType == ResType.CHAR_SEQUENCE || resType == ResType.DRAWABLE) {
type = TypedValue.TYPE_STRING;
} else if (resType == ResType.INTEGER) {
type = TypedValue.TYPE_INT_DEC;
} else if (resType == ResType.FLOAT || resType == ResType.FRACTION) {
type = TypedValue.TYPE_FLOAT;
} else if (resType == ResType.BOOLEAN) {
type = TypedValue.TYPE_INT_BOOLEAN;
} else if (resType == ResType.DIMEN) {
type = TypedValue.TYPE_DIMENSION;
} else if (resType == ResType.COLOR) {
type = TypedValue.TYPE_INT_COLOR_ARGB8;
} else if (resType == ResType.TYPED_ARRAY || resType == ResType.CHAR_SEQUENCE_ARRAY) {
type = TypedValue.TYPE_REFERENCE;
} else {
type = -1;
}
return type;
}

@HiddenApi @Implementation
public Number createTheme() {
synchronized (nativeThemes) {
Expand Down Expand Up @@ -711,7 +828,6 @@ private AttributeResource findAttributeValue(int resId, AttributeSet attributeSe
if (AttributeResource.isResourceReference(attributeSet.getAttributeValue(i))) {
referenceResId = attributeSet.getAttributeResourceValue(i, -1);
}

return new AttributeResource(resName, attributeSet.getAttributeValue(i), "fixme!!!", referenceResId);
}
}
Expand Down
Expand Up @@ -140,6 +140,16 @@ public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundEx
}
}

@Implementation
public TypedArray obtainTypedArray(int id) throws Resources.NotFoundException {
ShadowAssetManager shadowAssetManager = shadowOf(realResources.getAssets());
TypedArray typedArray = shadowAssetManager.getTypedArrayResource(realResources, id);
if (typedArray != null) {
return typedArray;
}
throw new Resources.NotFoundException("Typed array resource ID #0x" + Integer.toHexString(id));
}

public void setDensity(float density) {
this.density = density;
if (displayMetrics != null) {
Expand Down
Expand Up @@ -51,9 +51,10 @@ public static void dump(TypedArray typedArray) {
int[] data = ReflectionHelpers.getField(typedArray, "mData");

StringBuilder result = new StringBuilder();
for (int index = 0; index < data.length; index+= ShadowAssetManager.STYLE_NUM_ENTRIES) {
for (int index = 0; index < data.length; index+= ShadowAssetManager.STYLE_NUM_ENTRIES) {
final int type = data[index+ShadowAssetManager.STYLE_TYPE];
result.append("Type: ").append(TYPE_MAP.get(type)).append(System.lineSeparator());
result.append("Index: ").append(index / ShadowAssetManager.STYLE_NUM_ENTRIES).append(System.lineSeparator());
result.append(Strings.padEnd("Type: ", 25, ' ')).append(TYPE_MAP.get(type)).append(System.lineSeparator());
if (type != TypedValue.TYPE_NULL) {
result.append(Strings.padEnd("Style data: ", 25, ' ')).append(data[index+ ShadowAssetManager.STYLE_DATA]).append(System.lineSeparator());
result.append(Strings.padEnd("Asset cookie ", 25, ' ')).append(data[index+ShadowAssetManager.STYLE_ASSET_COOKIE]).append(System.lineSeparator());
Expand Down
18 changes: 14 additions & 4 deletions robolectric/src/test/java/org/robolectric/R.java
Expand Up @@ -84,10 +84,12 @@ public static final class id {
public static final int list_view_with_enum_scrollbar = 0x7f01003b;
public static final int action_search = 0x7f01003c;

public static final int idNotInXml = 0x7f01003d;
public static final int idIDeclaredInXml = 0x7f01003e;
public static int lib_button = 0x7f01003f;
public static int lib3_button = 0x7f010040;
public static final int id_declared_in_layout = 0x7f01003d;
public static final int id_declared_in_item_tag = 0x7f01003e;
public static final int id_with_string_value = 0x7f010042;
public static final int id_declared_in_id_tag = 0x7f010041;
public static final int lib_button = 0x7f01003f;
public static final int lib3_button = 0x7f010040;
}

public static final class string {
Expand Down Expand Up @@ -126,6 +128,8 @@ public static final class string {
public static final int say_it_with_item = 0x7f020120;
public static final int test_menu_2 = 0x7f020121;
public static final int also_in_all_libs = 0x7f020122;
public static final int typed_array_a = 0x7f020123;
public static final int typed_array_b = 0x7f020124;
}

public static final class plurals {
Expand All @@ -142,6 +146,9 @@ public static final class array {
public static final int empty_int_array = 0x7f040305;
public static final int with_references_int_array = 0x7f040306;
public static final int referenced_colors_int_array = 0x7f040307;
public static final int typed_array_values = 0x7f040308;
public static final int typed_array_references = 0x7f040309;
public static final int typed_array_with_resource_id = 0x7f04030A;
}

public static final class color {
Expand All @@ -160,6 +167,7 @@ public static final class color {
public static final int color_state_list = 0x7f05040c;
public static final int list_separator = 0x7f05040d;
public static final int custom_state_view_text_color = 0x7f05040e;
public static final int typed_array_orange = 0x7f05040f;
}

public static final class drawable {
Expand Down Expand Up @@ -367,6 +375,7 @@ public static final class integer {
public static final int reference_to_meaning_of_life = 0x7f0f0e09;
public static final int meaning_of_life_as_item = 0x7f0f0e0a;
public static final int scrollbar_style_ordinal_outside_overlay = 0x7f0f0e0b;
public static final int typed_array_5 = 0x7f0f0e0c;
}

public static final class bool {
Expand All @@ -378,6 +387,7 @@ public static final class bool {
public static final int true_as_item = 0x7f100f05;
public static final int different_resource_boolean=0x7f100f06;
public static final int value_only_present_in_w320dp=0x7f100f07;
public static final int typed_array_true=0x7f100f08;
}

public static final class style {
Expand Down
Expand Up @@ -43,6 +43,32 @@ public void shouldLoadDrawableBitmapResourcesDefinedByItemTag() throws Exception
assertThat((String) value.getData()).isEqualTo("@drawable/an_image");
}

@Test
public void shouldLoadIdResourcesDefinedByItemTag() throws Exception {
TypedResource value = resourceTable.getValue(new ResName("org.robolectric", "id", "id_declared_in_item_tag"), "");
assertThat(value).isNotNull();
assertThat(value.getResType()).isEqualTo(ResType.CHAR_SEQUENCE);
assertThat(value.isReference()).isFalse();
assertThat(value.asString()).isEqualTo("");
assertThat((String) value.getData()).isEqualTo("");
}

@Test
public void shouldLoadIdResourcesDefinedByIdTag() throws Exception {
TypedResource value = resourceTable.getValue(new ResName("org.robolectric", "id", "id_declared_in_id_tag"), "");
assertThat(value).isNotNull();
assertThat(value.getResType()).isEqualTo(ResType.CHAR_SEQUENCE);
assertThat(value.isReference()).isFalse();
assertThat(value.asString()).isEqualTo("");
assertThat((String) value.getData()).isEqualTo("");
}

@Test
public void whenIdItemsHaveStringContent_shouldLoadIdResourcesDefinedByItemTag() throws Exception {
TypedResource value2 = resourceTable.getValue(new ResName("org.robolectric", "id", "id_with_string_value"), "");
assertThat(value2.asString()).isEqualTo("string value");
}

@Test
public void shouldLoadResourcesFromGradleOutputDirectories() {
TypedResource value = gradleResourceTable.getValue(new ResName("org.robolectric.gradleapp", "string", "from_gradle_output"), "");
Expand Down

0 comments on commit fbf415e

Please sign in to comment.