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