• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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