1 package org.robolectric.shadows; 2 3 import static org.robolectric.res.android.AttributeResolution.STYLE_ASSET_COOKIE; 4 import static org.robolectric.res.android.AttributeResolution.STYLE_CHANGING_CONFIGURATIONS; 5 import static org.robolectric.res.android.AttributeResolution.STYLE_DATA; 6 import static org.robolectric.res.android.AttributeResolution.STYLE_DENSITY; 7 import static org.robolectric.res.android.AttributeResolution.STYLE_NUM_ENTRIES; 8 import static org.robolectric.res.android.AttributeResolution.STYLE_RESOURCE_ID; 9 import static org.robolectric.res.android.AttributeResolution.STYLE_TYPE; 10 import static org.robolectric.shadow.api.Shadow.directlyOn; 11 12 import android.annotation.StyleableRes; 13 import android.content.res.Resources; 14 import android.content.res.TypedArray; 15 import android.os.Build; 16 import android.util.TypedValue; 17 import com.google.common.base.Strings; 18 import com.google.common.collect.ImmutableMap; 19 import org.robolectric.RuntimeEnvironment; 20 import org.robolectric.annotation.HiddenApi; 21 import org.robolectric.annotation.Implementation; 22 import org.robolectric.annotation.Implements; 23 import org.robolectric.annotation.RealObject; 24 import org.robolectric.shadow.api.Shadow; 25 import org.robolectric.util.ReflectionHelpers; 26 import org.robolectric.util.ReflectionHelpers.ClassParameter; 27 28 @SuppressWarnings({"UnusedDeclaration"}) 29 @Implements(TypedArray.class) 30 public class ShadowTypedArray { 31 @RealObject private TypedArray realTypedArray; 32 private CharSequence[] stringData; 33 public String positionDescription; 34 create(Resources realResources, int[] attrs, int[] data, int[] indices, int len, CharSequence[] stringData)35 public static TypedArray create(Resources realResources, int[] attrs, int[] data, int[] indices, int len, CharSequence[] stringData) { 36 TypedArray typedArray; 37 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { 38 typedArray = ReflectionHelpers.callConstructor(TypedArray.class, 39 ClassParameter.from(Resources.class, realResources)); 40 ReflectionHelpers.setField(typedArray, "mData", data); 41 ReflectionHelpers.setField(typedArray, "mLength", len); 42 ReflectionHelpers.setField(typedArray, "mIndices", indices); 43 } else { 44 typedArray = ReflectionHelpers.callConstructor(TypedArray.class, 45 ClassParameter.from(Resources.class, realResources), 46 ClassParameter.from(int[].class, data), 47 ClassParameter.from(int[].class, indices), 48 ClassParameter.from(int.class, len)); 49 } 50 51 ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); 52 shadowTypedArray.stringData = stringData; 53 return typedArray; 54 } 55 56 @HiddenApi @Implementation loadStringValueAt(int index)57 protected CharSequence loadStringValueAt(int index) { 58 if (ShadowAssetManager.useLegacy()) { 59 return stringData[index / STYLE_NUM_ENTRIES]; 60 } else { 61 return directlyOn(realTypedArray, TypedArray.class, "loadStringValueAt", 62 new ClassParameter(int.class, index)); 63 } 64 } 65 66 @Implementation getNonResourceString(@tyleableRes int index)67 protected String getNonResourceString(@StyleableRes int index) { 68 return directlyOn(realTypedArray, TypedArray.class).getString(index); 69 } 70 71 @Implementation getNonConfigurationString(@tyleableRes int index, int allowedChangingConfigs)72 protected String getNonConfigurationString(@StyleableRes int index, int allowedChangingConfigs) { 73 return directlyOn(realTypedArray, TypedArray.class).getString(index); 74 } 75 76 @Implementation getPositionDescription()77 protected String getPositionDescription() { 78 if (ShadowAssetManager.useLegacy()) { 79 return positionDescription; 80 } else { 81 return directlyOn(realTypedArray, TypedArray.class, "getPositionDescription"); 82 } 83 } 84 dump(TypedArray typedArray)85 public static void dump(TypedArray typedArray) { 86 int[] data = ReflectionHelpers.getField(typedArray, "mData"); 87 88 StringBuilder result = new StringBuilder(); 89 for (int index = 0; index < data.length; index+= STYLE_NUM_ENTRIES) { 90 final int type = data[index+STYLE_TYPE]; 91 result.append("Index: ").append(index / STYLE_NUM_ENTRIES).append(System.lineSeparator()); 92 result.append(Strings.padEnd("Type: ", 25, ' ')).append(TYPE_MAP.get(type)).append(System.lineSeparator()); 93 if (type != TypedValue.TYPE_NULL) { 94 result.append(Strings.padEnd("Style data: ", 25, ' ')).append(data[index+ STYLE_DATA]).append(System.lineSeparator()); 95 result.append(Strings.padEnd("Asset cookie ", 25, ' ')).append(data[index+STYLE_ASSET_COOKIE]).append(System.lineSeparator()); 96 result.append(Strings.padEnd("Style resourceId: ", 25, ' ')).append(data[index+ STYLE_RESOURCE_ID]).append(System.lineSeparator()); 97 result.append(Strings.padEnd("Changing configurations ", 25, ' ')).append(data[index+STYLE_CHANGING_CONFIGURATIONS]).append(System.lineSeparator()); 98 result.append(Strings.padEnd("Style density: ", 25, ' ')).append(data[index+STYLE_DENSITY]).append(System.lineSeparator()); 99 if (type == TypedValue.TYPE_STRING) { 100 ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); 101 result.append(Strings.padEnd("Style value: ", 25, ' ')).append(shadowTypedArray.loadStringValueAt(index)).append(System.lineSeparator()); 102 } 103 } 104 result.append(System.lineSeparator()); 105 } 106 System.out.println(result.toString()); 107 } 108 109 private static final ImmutableMap<Integer, String> TYPE_MAP = ImmutableMap.<Integer, String>builder() 110 .put(TypedValue.TYPE_NULL, "TYPE_NULL") 111 .put(TypedValue.TYPE_REFERENCE, "TYPE_REFERENCE") 112 .put(TypedValue.TYPE_ATTRIBUTE, "TYPE_ATTRIBUTE") 113 .put(TypedValue.TYPE_STRING, "TYPE_STRING") 114 .put(TypedValue.TYPE_FLOAT, "TYPE_FLOAT") 115 .put(TypedValue.TYPE_DIMENSION, "TYPE_DIMENSION") 116 .put(TypedValue.TYPE_FRACTION, "TYPE_FRACTION") 117 .put(TypedValue.TYPE_INT_DEC, "TYPE_INT_DEC") 118 .put(TypedValue.TYPE_INT_HEX, "TYPE_INT_HEX") 119 .put(TypedValue.TYPE_INT_BOOLEAN, "TYPE_INT_BOOLEAN") 120 .put(TypedValue.TYPE_INT_COLOR_ARGB8, "TYPE_INT_COLOR_ARGB8") 121 .put(TypedValue.TYPE_INT_COLOR_RGB8, "TYPE_INT_COLOR_RGB8") 122 .put(TypedValue.TYPE_INT_COLOR_ARGB4, "TYPE_INT_COLOR_ARGB4") 123 .put(TypedValue.TYPE_INT_COLOR_RGB4, "TYPE_INT_COLOR_RGB4") 124 .build(); 125 126 } 127