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 SystemLogEInterceptor(), 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 public static class SystemLogEInterceptor extends Interceptor { 200 public SystemLogEInterceptor() { 201 super(new MethodRef(System.class.getName(), "logE")); 202 } 203 204 static void logE(Object... params) { 205 String message = "System.logE: "; 206 for (Object param : params) { 207 message += param.toString(); 208 } 209 System.err.println(message); 210 } 211 212 @Override 213 public Function<Object, Object> handle(MethodSignature methodSignature) { 214 return new Function<Object, Object>() { 215 @Override 216 public Object call(Class<?> theClass, Object value, Object[] params) { 217 logE(params); 218 return null; 219 } 220 }; 221 } 222 223 @Override 224 public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException { 225 return lookup.findStatic(getClass(), "logE", 226 methodType(void.class, Object[].class)); 227 } 228 } 229 230 public static class NoOpInterceptor extends Interceptor { 231 public NoOpInterceptor() { 232 super( 233 new MethodRef("java.lang.System", "loadLibrary"), 234 new MethodRef("android.os.StrictMode", "trackActivity"), 235 new MethodRef("android.os.StrictMode", "incrementExpectedActivityCount"), 236 new MethodRef("android.util.LocaleUtil", "getLayoutDirectionFromLocale"), 237 new MethodRef("android.view.FallbackEventHandler", "*"), 238 new MethodRef("android.view.IWindowSession", "*") 239 ); 240 } 241 242 @Override public Function<Object, Object> handle(MethodSignature methodSignature) { 243 return returnDefaultValue(methodSignature); 244 } 245 246 @Override public MethodHandle getMethodHandle(String methodName, MethodType type) throws NoSuchMethodException, IllegalAccessException { 247 MethodHandle nothing = constant(Void.class, null).asType(methodType(void.class)); 248 249 if (type.parameterCount() != 0) { 250 return dropArguments(nothing, 0, type.parameterArray()); 251 } else { 252 return nothing; 253 } 254 } 255 } 256 } 257