1 package org.robolectric.shadows; 2 3 import static org.robolectric.util.reflector.Reflector.reflector; 4 5 import android.content.res.AssetManager; 6 import android.content.res.AssetManager.AssetInputStream; 7 import android.content.res.XmlBlock; 8 import android.util.TypedValue; 9 import dalvik.system.VMRuntime; 10 import java.io.InputStream; 11 import java.nio.file.Path; 12 import java.util.Collection; 13 import java.util.Locale; 14 import java.util.Objects; 15 import org.robolectric.annotation.Implementation; 16 import org.robolectric.annotation.Implements; 17 import org.robolectric.annotation.ReflectorObject; 18 import org.robolectric.shadow.api.Shadow; 19 import org.robolectric.util.PerfStatsCollector; 20 import org.robolectric.util.ReflectionHelpers; 21 import org.robolectric.util.ReflectionHelpers.ClassParameter; 22 import org.robolectric.util.reflector.Accessor; 23 import org.robolectric.util.reflector.Direct; 24 import org.robolectric.util.reflector.ForType; 25 import org.robolectric.versioning.AndroidVersions.V; 26 27 @Implements( 28 value = AssetManager.class, 29 minSdk = V.SDK_INT, 30 callNativeMethodsByDefault = true, 31 shadowPicker = ShadowAssetManager.Picker.class) 32 public class ShadowNativeAssetManager extends ShadowAssetManager { 33 34 @ReflectorObject private AssetManagerReflector assetManagerReflector; 35 36 @Override getAllAssetDirs()37 Collection<Path> getAllAssetDirs() { 38 throw new UnsupportedOperationException(); 39 } 40 41 @Override getNativePtr()42 long getNativePtr() { 43 throw new UnsupportedOperationException(); 44 } 45 46 @Implementation open(String fileName, int accessMode)47 protected InputStream open(String fileName, int accessMode) { 48 // intercept real method to handle nine patch for LEGACY graphics mode 49 InputStream is = assetManagerReflector.open(fileName, accessMode); 50 setNinePatch(fileName, is); 51 return is; 52 } 53 setNinePatch(String fileName, InputStream is)54 private static void setNinePatch(String fileName, InputStream is) { 55 if (is instanceof AssetInputStream) { 56 ShadowNativeAssetInputStream snais = Shadow.extract(is); 57 // this is a dubious assumption, but this is the logic with BINARY resources mode: 58 // assume nine patch based on file name 59 if (fileName != null && fileName.toLowerCase(Locale.ENGLISH).endsWith(".9.png")) { 60 snais.setNinePatch(true); 61 } 62 } 63 } 64 65 @Implementation openNonAsset(int cookie, String fileName, int accessMode)66 protected InputStream openNonAsset(int cookie, String fileName, int accessMode) { 67 // intercept real method to handle nine patch for LEGACY graphics mode 68 InputStream is = assetManagerReflector.openNonAsset(cookie, fileName, accessMode); 69 setNinePatch(fileName, is); 70 return is; 71 } 72 73 /** 74 * Use a similar implementation as applyStyle$ravenwood as workaround for allocating pinned 75 * (non-movable) array objects. 76 */ 77 @Implementation applyStyle( long themePtr, int defStyleAttr, int defStyleRes, XmlBlock.Parser parser, int[] inAttrs, long outValuesAddress, long outIndicesAddress)78 protected void applyStyle( 79 long themePtr, 80 int defStyleAttr, 81 int defStyleRes, 82 XmlBlock.Parser parser, 83 int[] inAttrs, 84 long outValuesAddress, 85 long outIndicesAddress) { 86 Objects.requireNonNull(inAttrs, "inAttrs"); 87 PerfStatsCollector.getInstance() 88 .measure( 89 "native applyStyle", 90 () -> { 91 ShadowVMRuntime shadowVmRuntime = Shadow.extract(VMRuntime.getRuntime()); 92 int[] outValues = (int[]) shadowVmRuntime.getObjectForAddress(outValuesAddress); 93 int[] outIndices = (int[]) shadowVmRuntime.getObjectForAddress(outIndicesAddress); 94 synchronized (this) { 95 // Need to synchronize on AssetManager because we will be accessing 96 // the native implementation of AssetManager. 97 assetManagerReflector.ensureValidLocked(); 98 long xmlParserPtr = 99 parser != null 100 ? reflector(XmlBlockParserReflector.class, parser).getParseState() 101 : 0; 102 ReflectionHelpers.callStaticMethod( 103 AssetManager.class, 104 Shadow.directNativeMethodName( 105 AssetManager.class.getName(), "nativeApplyStyleWithArray"), 106 ClassParameter.from(long.class, assetManagerReflector.getObject()), 107 ClassParameter.from(long.class, themePtr), 108 ClassParameter.from(int.class, defStyleAttr), 109 ClassParameter.from(int.class, defStyleRes), 110 ClassParameter.from(long.class, xmlParserPtr), 111 ClassParameter.from(int[].class, inAttrs), 112 ClassParameter.from(int[].class, outValues), 113 ClassParameter.from(int[].class, outIndices)); 114 } 115 }); 116 } 117 118 @Implementation nativeGetResourceValue( long ptr, int resid, short density, TypedValue typed_value, boolean resolve_references)119 protected static int nativeGetResourceValue( 120 long ptr, int resid, short density, TypedValue typed_value, boolean resolve_references) { 121 return PerfStatsCollector.getInstance() 122 .measure( 123 "native nativeGetResourceValue", 124 () -> 125 ReflectionHelpers.callStaticMethod( 126 AssetManager.class, 127 Shadow.directNativeMethodName( 128 AssetManager.class.getName(), "nativeGetResourceValue"), 129 ClassParameter.from(long.class, ptr), 130 ClassParameter.from(int.class, resid), 131 ClassParameter.from(short.class, density), 132 ClassParameter.from(TypedValue.class, typed_value), 133 ClassParameter.from(boolean.class, resolve_references))); 134 } 135 136 @ForType(AssetManager.class) 137 interface AssetManagerReflector { 138 @Accessor("mObject") getObject()139 long getObject(); 140 ensureValidLocked()141 void ensureValidLocked(); 142 143 @Direct open(String fileName, int accessMode)144 InputStream open(String fileName, int accessMode); 145 146 @Direct openNonAsset(int cookie, String fileName, int accessMode)147 InputStream openNonAsset(int cookie, String fileName, int accessMode); 148 } 149 150 @ForType(XmlBlock.Parser.class) 151 interface XmlBlockParserReflector { 152 @Accessor("mParseState") getParseState()153 long getParseState(); 154 } 155 } 156