• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.android;
2 
3 import static java.lang.invoke.MethodHandles.constant;
4 import static java.lang.invoke.MethodHandles.dropArguments;
5 import static java.lang.invoke.MethodType.methodType;
6 import static java.util.Arrays.asList;
7 
8 import android.content.Context;
9 import java.lang.invoke.MethodHandle;
10 import java.lang.invoke.MethodHandles;
11 import java.lang.invoke.MethodType;
12 import java.util.Collection;
13 import java.util.LinkedHashMap;
14 import java.util.Locale;
15 import javax.annotation.Nullable;
16 import org.robolectric.internal.bytecode.Interceptor;
17 import org.robolectric.internal.bytecode.MethodRef;
18 import org.robolectric.internal.bytecode.MethodSignature;
19 import org.robolectric.shadows.ShadowSystemClock;
20 import org.robolectric.shadows.ShadowWindow;
21 import org.robolectric.util.Function;
22 import org.robolectric.util.ReflectionHelpers;
23 
24 public class AndroidInterceptors {
25   private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
26 
all()27   public static Collection<Interceptor> all() {
28     return asList(
29         new LinkedHashMapEldestInterceptor(),
30         new PolicyManagerMakeNewWindowInterceptor(),
31         new SystemTimeInterceptor(),
32         new SystemArrayCopyInterceptor(),
33         new LocaleAdjustLanguageCodeInterceptor(),
34         new SystemLogInterceptor(),
35         new NoOpInterceptor()
36     );
37   }
38 
39 //  @Intercept(value = LinkedHashMap.class, method = "eldest")
40   public static class LinkedHashMapEldestInterceptor extends Interceptor {
LinkedHashMapEldestInterceptor()41     public LinkedHashMapEldestInterceptor() {
42       super(new MethodRef(LinkedHashMap.class, "eldest"));
43     }
44 
45     @Nullable
eldest(LinkedHashMap map)46     static Object eldest(LinkedHashMap map) {
47       return map.isEmpty() ? null : map.entrySet().iterator().next();
48     }
49 
50     @Override
handle(MethodSignature methodSignature)51     public Function<Object, Object> handle(MethodSignature methodSignature) {
52       return new Function<Object, Object>() {
53         @Override
54         public Object call(Class<?> theClass, Object value, Object[] params) {
55           return eldest((LinkedHashMap) value);
56         }
57       };
58     }
59 
60     @Override
getMethodHandle(String methodName, MethodType type)61     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
62       return lookup.findStatic(getClass(), "eldest",
63           methodType(Object.class, LinkedHashMap.class));
64     }
65   }
66 
67   public static class PolicyManagerMakeNewWindowInterceptor extends Interceptor {
68     public PolicyManagerMakeNewWindowInterceptor() {
69       super(new MethodRef("com.android.internal.policy.PolicyManager", "makeNewWindow"));
70     }
71 
72     @Override
73     public Function<Object, Object> handle(MethodSignature methodSignature) {
74       return new Function<Object, Object>() {
75         @Override
76         public Object call(Class<?> theClass, Object value, Object[] params) {
77           ClassLoader cl = theClass.getClassLoader();
78 
79           try {
80             Class<?> shadowWindowClass = cl.loadClass("org.robolectric.shadows.ShadowWindow");
81             Class<?> activityClass = cl.loadClass(Context.class.getName());
82             Object context = params[0];
83             return ReflectionHelpers.callStaticMethod(shadowWindowClass, "create", ReflectionHelpers.ClassParameter.from(activityClass, context));
84           } catch (ClassNotFoundException e) {
85             throw new RuntimeException(e);
86           }
87         }
88       };
89     }
90 
91     @Override
92     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
93       Class<?> shadowWindowClass;
94       try {
95         shadowWindowClass = type.returnType().getClassLoader().loadClass(ShadowWindow.class.getName());
96       } catch (ClassNotFoundException e) {
97         throw new RuntimeException(e);
98       }
99       return lookup.in(type.returnType()).findStatic(shadowWindowClass, "create", type);
100     }
101   }
102 
103   public static class SystemTimeInterceptor extends Interceptor {
104     public SystemTimeInterceptor() {
105       super(new MethodRef(System.class, "nanoTime"), new MethodRef(System.class, "currentTimeMillis"));
106     }
107 
108     @Override
109     public Function<Object, Object> handle(final MethodSignature methodSignature) {
110       return new Function<Object, Object>() {
111         @Override
112         public Object call(Class<?> theClass, Object value, Object[] params) {
113           ClassLoader cl = theClass.getClassLoader();
114           try {
115             Class<?> shadowSystemClockClass = cl.loadClass("org.robolectric.shadows.ShadowSystemClock");
116             return ReflectionHelpers.callStaticMethod(shadowSystemClockClass, methodSignature.methodName);
117           } catch (ClassNotFoundException e) {
118             throw new RuntimeException(e);
119           }
120         }
121       };
122     }
123 
124     @Override
125     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
126       switch (methodName) {
127         case "nanoTime":
128           return lookup.findStatic(ShadowSystemClock.class,
129               "nanoTime", methodType(long.class));
130         case "currentTimeMillis":
131           return lookup.findStatic(ShadowSystemClock.class,
132               "currentTimeMillis", methodType(long.class));
133       }
134       throw new UnsupportedOperationException();
135     }
136   }
137 
138   public static class SystemArrayCopyInterceptor extends Interceptor {
139     public SystemArrayCopyInterceptor() {
140       super(new MethodRef(System.class, "arraycopy"));
141     }
142 
143     @Override
144     public Function<Object, Object> handle(MethodSignature methodSignature) {
145       return new Function<Object, Object>() {
146         @Override
147         public Object call(Class<?> theClass, Object value, Object[] params) {
148           //noinspection SuspiciousSystemArraycopy
149           System.arraycopy(params[0], (Integer) params[1], params[2], (Integer) params[3], (Integer) params[4]);
150           return null;
151         }
152       };
153     }
154 
155     @Override
156     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
157       return lookup.findStatic(System.class, "arraycopy",
158           methodType(void.class, Object.class, int.class, Object.class, int.class, int.class));
159     }
160   }
161 
162   public static class LocaleAdjustLanguageCodeInterceptor extends Interceptor {
163     public LocaleAdjustLanguageCodeInterceptor() {
164       super(new MethodRef(Locale.class, "adjustLanguageCode"));
165     }
166 
167     static String adjustLanguageCode(String languageCode) {
168       String adjusted = languageCode.toLowerCase(Locale.US);
169       // Map new language codes to the obsolete language
170       // codes so the correct resource bundles will be used.
171       if (languageCode.equals("he")) {
172         adjusted = "iw";
173       } else if (languageCode.equals("id")) {
174         adjusted = "in";
175       } else if (languageCode.equals("yi")) {
176         adjusted = "ji";
177       }
178 
179       return adjusted;
180     }
181 
182     @Override
183     public Function<Object, Object> handle(MethodSignature methodSignature) {
184       return new Function<Object, Object>() {
185         @Override
186         public Object call(Class<?> theClass, Object value, Object[] params) {
187           return adjustLanguageCode((String) params[0]);
188         }
189       };
190     }
191 
192     @Override
193     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
194       return lookup.findStatic(getClass(), "adjustLanguageCode",
195           methodType(String.class, String.class));
196     }
197   }
198 
199   /** AndroidInterceptor for System.logE and System.logW. */
200   public static class SystemLogInterceptor extends Interceptor {
201     public SystemLogInterceptor() {
202       super(
203           new MethodRef(System.class.getName(), "logE"),
204           new MethodRef(System.class.getName(), "logW"));
205     }
206 
207     static void logE(Object... params) {
208       log("System.logE: ", params);
209     }
210 
211     static void logW(Object... params) {
212       log("System.logW: ", params);
213     }
214 
215     static void log(String prefix, Object... params) {
216       StringBuilder message = new StringBuilder(prefix);
217       for (Object param : params) {
218         message.append(param.toString());
219       }
220       System.err.println(message);
221     }
222 
223     @Override
224     public Function<Object, Object> handle(MethodSignature methodSignature) {
225       return (theClass, value, params) -> {
226         if ("logE".equals(methodSignature.methodName)) {
227           logE(params);
228         } else if ("logW".equals(methodSignature.methodName)) {
229           logW(params);
230         }
231         return null;
232       };
233     }
234 
235     @Override
236     public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
237       return lookup.findStatic(getClass(), methodName, methodType(void.class, Object[].class));
238     }
239   }
240 
241   public static class NoOpInterceptor extends Interceptor {
242     public NoOpInterceptor() {
243       super(
244           new MethodRef("java.lang.System", "loadLibrary"),
245           new MethodRef("android.os.StrictMode", "trackActivity"),
246           new MethodRef("android.os.StrictMode", "incrementExpectedActivityCount"),
247           new MethodRef("android.util.LocaleUtil", "getLayoutDirectionFromLocale"),
248           new MethodRef("android.view.FallbackEventHandler", "*"),
249           new MethodRef("android.view.IWindowSession", "*")
250       );
251     }
252 
253     @Override public Function<Object, Object> handle(MethodSignature methodSignature) {
254       return returnDefaultValue(methodSignature);
255     }
256 
257     @Override public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException {
258       MethodHandle nothing = constant(Void.class, null).asType(methodType(void.class));
259 
260       if (type.parameterCount() != 0) {
261         return dropArguments(nothing, 0, type.parameterArray());
262       } else {
263         return nothing;
264       }
265     }
266   }
267 }
268