/* * Original work Copyright (c) 2005-2008, The Android Open Source Project * Modified work Copyright (c) 2013, rovo89 and Tungstwenty * Modified work Copyright (c) 2015, Alibaba Mobile Infrastructure (Android) Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.taobao.android.dexposed; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.WeakHashMap; import android.content.res.Resources; public class XposedHelpers { private static final HashMap<String, Field> fieldCache = new HashMap<String, Field>(); private static final HashMap<String, Method> methodCache = new HashMap<String, Method>(); private static final HashMap<String, Constructor<?>> constructorCache = new HashMap<String, Constructor<?>>(); private static final WeakHashMap<Object, HashMap<String, Object>> additionalFields = new WeakHashMap<Object, HashMap<String, Object>>(); /** * Look up a class with the specified class loader (or the boot class loader if * <code>classLoader</code> is <code>null</code>). * <p>Class names can be specified in different formats: * <ul><li>java.lang.Integer * <li>int * <li>int[] * <li>[I * <li>java.lang.String[] * <li>[Ljava.lang.String; * <li>android.app.ActivityThread.ResourcesKey * <li>android.app.ActivityThread$ResourcesKey * <li>android.app.ActivityThread$ResourcesKey[]</ul> * <p>A {@link ClassNotFoundError} is thrown in case the class was not found. */ public static Class<?> findClass(String className, ClassLoader classLoader) { if (classLoader == null) classLoader = DexposedBridge.BOOTCLASSLOADER; try { return ClassUtils.getClass(classLoader, className, false); } catch (ClassNotFoundException e) { throw new ClassNotFoundError(e); } } /** * Look up a field in a class and set it to accessible. The result is cached. * If the field was not found, a {@link NoSuchFieldError} will be thrown. */ public static Field findField(Class<?> clazz, String fieldName) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append('#'); sb.append(fieldName); String fullFieldName = sb.toString(); if (fieldCache.containsKey(fullFieldName)) { Field field = fieldCache.get(fullFieldName); if (field == null) throw new NoSuchFieldError(fullFieldName); return field; } try { Field field = findFieldRecursiveImpl(clazz, fieldName); field.setAccessible(true); fieldCache.put(fullFieldName, field); return field; } catch (NoSuchFieldException e) { fieldCache.put(fullFieldName, null); throw new NoSuchFieldError(fullFieldName); } } private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { while (true) { clazz = clazz.getSuperclass(); if (clazz == null || clazz.equals(Object.class)) break; try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException ignored) {} } throw e; } } /** * Returns the first field of the given type in a class. * Might be useful for Proguard'ed classes to identify fields with unique types. * If no matching field was not found, a {@link NoSuchFieldError} will be thrown. */ public static Field findFirstFieldByExactType(Class<?> clazz, Class<?> type) { Class<?> clz = clazz; do { for (Field field : clz.getDeclaredFields()) { if (field.getType() == type) { field.setAccessible(true); return field; } } } while ((clz = clz.getSuperclass()) != null); throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName()); } /** * Look up a method in a class and set it to accessible. The result is cached. * If the method was not found, a {@link NoSuchMethodError} will be thrown. * * <p>The parameter types may either be specified as <code>Class</code> or <code>String</code> * objects. In the latter case, the class is looked up using {@link #findClass} with the same * class loader as the method's class. */ public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) { Class<?>[] parameterClasses = null; for (int i = parameterTypes.length - 1; i >= 0; i--) { Object type = parameterTypes[i]; if (type == null) throw new ClassNotFoundError("parameter type must not be null", null); // ignore trailing callback if (type instanceof XC_MethodHook) continue; if (parameterClasses == null) parameterClasses = new Class<?>[i+1]; if (type instanceof Class) parameterClasses[i] = (Class<?>) type; else if (type instanceof String) parameterClasses[i] = findClass((String) type, clazz.getClassLoader()); else throw new ClassNotFoundError("parameter type must either be specified as Class or String", null); } // if there are no arguments for the method if (parameterClasses == null) parameterClasses = new Class<?>[0]; return findMethodExact(clazz, methodName, parameterClasses); } // /** // * Look up a method and place a hook on it. The last argument must be the callback for the hook. // * @see #findMethodExact(Class, String, Object...) // */ // public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) { // if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook)) // throw new IllegalArgumentException("no callback defined"); // // XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1]; // Method m = findMethodExact(clazz, methodName, parameterTypesAndCallback); // // return XposedBridge.hookMethod(m, callback); // } /** @see #findMethodExact(Class, String, Object...) */ public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) { return findMethodExact(findClass(className, classLoader), methodName, parameterTypes); } // /** @see #findAndHookMethod(Class, String, Object...) */ // public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) { // return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback); // } /** * Look up a method in a class and set it to accessible. The result is cached. * If the method was not found, a {@link NoSuchMethodError} will be thrown. */ public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append('#'); sb.append(methodName); sb.append(getParametersString(parameterTypes)); sb.append("#exact"); String fullMethodName = sb.toString(); if (methodCache.containsKey(fullMethodName)) { Method method = methodCache.get(fullMethodName); if (method == null) throw new NoSuchMethodError(fullMethodName); return method; } try { Method method = clazz.getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); methodCache.put(fullMethodName, method); return method; } catch (NoSuchMethodException e) { methodCache.put(fullMethodName, null); throw new NoSuchMethodError(fullMethodName); } } /** * Returns an array of all methods in a class with the specified parameter types. * * The return type is optional, it will not be compared if it is {@code null}. * Use {@code void.class} if you want to search for methods returning nothing. */ public static Method[] findMethodsByExactParameters(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) { List<Method> result = new LinkedList<Method>(); for (Method method : clazz.getDeclaredMethods()) { if (returnType != null && returnType != method.getReturnType()) continue; Class<?>[] methodParameterTypes = method.getParameterTypes(); if (parameterTypes.length != methodParameterTypes.length) continue; boolean match = true; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] != methodParameterTypes[i]) { match = false; break; } } if (!match) continue; method.setAccessible(true); result.add(method); } return result.toArray(new Method[result.size()]); } /** * Look up a method in a class and set it to accessible. The result is cached. * This does not only look for exact matches, but for the closest match. * If the method was not found, a {@link NoSuchMethodError} will be thrown. * @see MethodUtils#getMatchingAccessibleMethod */ public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>... parameterTypes) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append('#'); sb.append(methodName); sb.append(getParametersString(parameterTypes)); sb.append("#bestmatch"); String fullMethodName = sb.toString(); if (methodCache.containsKey(fullMethodName)) { Method method = methodCache.get(fullMethodName); if (method == null) throw new NoSuchMethodError(fullMethodName); return method; } try { Method method = findMethodExact(clazz, methodName, parameterTypes); methodCache.put(fullMethodName, method); return method; } catch (NoSuchMethodError ignored) {} Method bestMatch = null; Class<?> clz = clazz; boolean considerPrivateMethods = true; do { for (Method method : clz.getDeclaredMethods()) { // don't consider private methods of superclasses if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers())) continue; // compare name and parameters // if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) { // // get accessible version of method // if (bestMatch == null || MemberUtils.compareParameterTypes( // method.getParameterTypes(), // bestMatch.getParameterTypes(), // parameterTypes) < 0) { // bestMatch = method; // } // } } considerPrivateMethods = false; } while ((clz = clz.getSuperclass()) != null); if (bestMatch != null) { bestMatch.setAccessible(true); methodCache.put(fullMethodName, bestMatch); return bestMatch; } else { NoSuchMethodError e = new NoSuchMethodError(fullMethodName); methodCache.put(fullMethodName, null); throw e; } } /** * Look up a method in a class and set it to accessible. Parameter types are * determined from the <code>args</code> for the method call. The result is cached. * This does not only look for exact matches, but for the closest match. * If the method was not found, a {@link NoSuchMethodError} will be thrown. */ public static Method findMethodBestMatch(Class<?> clazz, String methodName, Object... args) { return findMethodBestMatch(clazz, methodName, getParameterTypes(args)); } /** * Look up a method in a class and set it to accessible. Parameter types are * preferably taken from the <code>parameterTypes</code>. Any item in this array that * is <code>null</code> is determined from the corresponding item in <code>args</code>. * The result is cached. * This does not only look for exact matches, but for the closest match. * If the method was not found, a {@link NoSuchMethodError} will be thrown. */ public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object[] args) { Class<?>[] argsClasses = null; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] != null) continue; if (argsClasses == null) argsClasses = getParameterTypes(args); parameterTypes[i] = argsClasses[i]; } return findMethodBestMatch(clazz, methodName, parameterTypes); } /** * Return an array with the classes of the given objects */ public static Class<?>[] getParameterTypes(Object... args) { Class<?>[] clazzes = new Class<?>[args.length]; for (int i = 0; i < args.length; i++) { clazzes[i] = (args[i] != null) ? args[i].getClass() : null; } return clazzes; } /** * Return an array with the classes of the given objects */ public static Class<?>[] getClassesAsArray(Class<?>... clazzes) { return clazzes; } private static String getParametersString(Class<?>... clazzes) { StringBuilder sb = new StringBuilder("("); boolean first = true; for (Class<?> clazz : clazzes) { if (first) first = false; else sb.append(","); if (clazz != null) sb.append(clazz.getCanonicalName()); else sb.append("null"); } sb.append(")"); return sb.toString(); } public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append(getParametersString(parameterTypes)); sb.append("#exact"); String fullConstructorName = sb.toString(); if (constructorCache.containsKey(fullConstructorName)) { Constructor<?> constructor = constructorCache.get(fullConstructorName); if (constructor == null) throw new NoSuchMethodError(fullConstructorName); return constructor; } try { Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); constructorCache.put(fullConstructorName, constructor); return constructor; } catch (NoSuchMethodException e) { constructorCache.put(fullConstructorName, null); throw new NoSuchMethodError(fullConstructorName); } } public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>... parameterTypes) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append(getParametersString(parameterTypes)); sb.append("#bestmatch"); String fullConstructorName = sb.toString(); if (constructorCache.containsKey(fullConstructorName)) { Constructor<?> constructor = constructorCache.get(fullConstructorName); if (constructor == null) throw new NoSuchMethodError(fullConstructorName); return constructor; } try { Constructor<?> constructor = findConstructorExact(clazz, parameterTypes); constructorCache.put(fullConstructorName, constructor); return constructor; } catch (NoSuchMethodError ignored) {} Constructor<?> bestMatch = null; Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { // compare name and parameters // if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) { // // get accessible version of method // if (bestMatch == null || MemberUtils.compareParameterTypes( // constructor.getParameterTypes(), // bestMatch.getParameterTypes(), // parameterTypes) < 0) { // bestMatch = constructor; // } // } } if (bestMatch != null) { bestMatch.setAccessible(true); constructorCache.put(fullConstructorName, bestMatch); return bestMatch; } else { NoSuchMethodError e = new NoSuchMethodError(fullConstructorName); constructorCache.put(fullConstructorName, null); throw e; } } public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Object... args) { return findConstructorBestMatch(clazz, getParameterTypes(args)); } public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>[] parameterTypes, Object[] args) { Class<?>[] argsClasses = null; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] != null) continue; if (argsClasses == null) argsClasses = getParameterTypes(args); parameterTypes[i] = argsClasses[i]; } return findConstructorBestMatch(clazz, parameterTypes); } public static class ClassNotFoundError extends Error { private static final long serialVersionUID = -1070936889459514628L; public ClassNotFoundError(Throwable cause) { super(cause); } public ClassNotFoundError(String detailMessage, Throwable cause) { super(detailMessage, cause); } } //################################################################################################# public static void setObjectField(Object obj, String fieldName, Object value) { try { findField(obj.getClass(), fieldName).set(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setBooleanField(Object obj, String fieldName, boolean value) { try { findField(obj.getClass(), fieldName).setBoolean(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setByteField(Object obj, String fieldName, byte value) { try { findField(obj.getClass(), fieldName).setByte(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setCharField(Object obj, String fieldName, char value) { try { findField(obj.getClass(), fieldName).setChar(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setDoubleField(Object obj, String fieldName, double value) { try { findField(obj.getClass(), fieldName).setDouble(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setFloatField(Object obj, String fieldName, float value) { try { findField(obj.getClass(), fieldName).setFloat(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setIntField(Object obj, String fieldName, int value) { try { findField(obj.getClass(), fieldName).setInt(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setLongField(Object obj, String fieldName, long value) { try { findField(obj.getClass(), fieldName).setLong(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setShortField(Object obj, String fieldName, short value) { try { findField(obj.getClass(), fieldName).setShort(obj, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# public static Object getObjectField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).get(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** For inner classes, return the "this" reference of the surrounding class */ public static Object getSurroundingThis(Object obj) { return getObjectField(obj, "this$0"); } public static boolean getBooleanField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getBoolean(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static byte getByteField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getByte(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static char getCharField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getChar(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static double getDoubleField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getDouble(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static float getFloatField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getFloat(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static int getIntField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getInt(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static long getLongField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getLong(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static short getShortField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getShort(obj); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# public static void setStaticObjectField(Class<?> clazz, String fieldName, Object value) { try { findField(clazz, fieldName).set(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticBooleanField(Class<?> clazz, String fieldName, boolean value) { try { findField(clazz, fieldName).setBoolean(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticByteField(Class<?> clazz, String fieldName, byte value) { try { findField(clazz, fieldName).setByte(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticCharField(Class<?> clazz, String fieldName, char value) { try { findField(clazz, fieldName).setChar(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticDoubleField(Class<?> clazz, String fieldName, double value) { try { findField(clazz, fieldName).setDouble(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticFloatField(Class<?> clazz, String fieldName, float value) { try { findField(clazz, fieldName).setFloat(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticIntField(Class<?> clazz, String fieldName, int value) { try { findField(clazz, fieldName).setInt(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticLongField(Class<?> clazz, String fieldName, long value) { try { findField(clazz, fieldName).setLong(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static void setStaticShortField(Class<?> clazz, String fieldName, short value) { try { findField(clazz, fieldName).setShort(null, value); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# public static Object getStaticObjectField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).get(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static boolean getStaticBooleanField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getBoolean(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static byte getStaticByteField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getByte(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static char getStaticCharField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getChar(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static double getStaticDoubleField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getDouble(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static float getStaticFloatField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getFloat(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static int getStaticIntField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getInt(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static long getStaticLongField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getLong(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } public static short getStaticShortField(Class<?> clazz, String fieldName) { try { return findField(clazz, fieldName).getShort(null); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# /** * Call instance or static method <code>methodName</code> for object <code>obj</code> with the arguments * <code>args</code>. The types for the arguments will be determined automaticall from <code>args</code> */ public static Object callMethod(Object obj, String methodName, Object... args) { try { return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Call instance or static method <code>methodName</code> for object <code>obj</code> with the arguments * <code>args</code>. The types for the arguments will be taken from <code>parameterTypes</code>. * This array can have items that are <code>null</code>. In this case, the type for this parameter * is determined from <code>args</code>. */ public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) { try { return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Call static method <code>methodName</code> for class <code>clazz</code> with the arguments * <code>args</code>. The types for the arguments will be determined automaticall from <code>args</code> */ public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) { try { return findMethodBestMatch(clazz, methodName, args).invoke(null, args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Call static method <code>methodName</code> for class <code>clazz</code> with the arguments * <code>args</code>. The types for the arguments will be taken from <code>parameterTypes</code>. * This array can have items that are <code>null</code>. In this case, the type for this parameter * is determined from <code>args</code>. */ public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) { try { return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } public static class InvocationTargetError extends Error { private static final long serialVersionUID = -1070936889459514628L; public InvocationTargetError(Throwable cause) { super(cause); } public InvocationTargetError(String detailMessage, Throwable cause) { super(detailMessage, cause); } } //################################################################################################# public static Object newInstance(Class<?> clazz, Object... args) { try { return findConstructorBestMatch(clazz, args).newInstance(args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } catch (InstantiationException e) { throw new InstantiationError(e.getMessage()); } } public static Object newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args) { try { return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args); } catch (IllegalAccessException e) { // should not happen DexposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } catch (InstantiationException e) { throw new InstantiationError(e.getMessage()); } } //################################################################################################# public static Object setAdditionalInstanceField(Object obj, String key, Object value) { if (obj == null) throw new NullPointerException("object must not be null"); if (key == null) throw new NullPointerException("key must not be null"); HashMap<String, Object> objectFields; synchronized (additionalFields) { objectFields = additionalFields.get(obj); if (objectFields == null) { objectFields = new HashMap<String, Object>(); additionalFields.put(obj, objectFields); } } synchronized (objectFields) { return objectFields.put(key, value); } } public static Object getAdditionalInstanceField(Object obj, String key) { if (obj == null) throw new NullPointerException("object must not be null"); if (key == null) throw new NullPointerException("key must not be null"); HashMap<String, Object> objectFields; synchronized (additionalFields) { objectFields = additionalFields.get(obj); if (objectFields == null) return null; } synchronized (objectFields) { return objectFields.get(key); } } public static Object removeAdditionalInstanceField(Object obj, String key) { if (obj == null) throw new NullPointerException("object must not be null"); if (key == null) throw new NullPointerException("key must not be null"); HashMap<String, Object> objectFields; synchronized (additionalFields) { objectFields = additionalFields.get(obj); if (objectFields == null) return null; } synchronized (objectFields) { return objectFields.remove(key); } } public static Object setAdditionalStaticField(Object obj, String key, Object value) { return setAdditionalInstanceField(obj.getClass(), key, value); } public static Object getAdditionalStaticField(Object obj, String key) { return getAdditionalInstanceField(obj.getClass(), key); } public static Object removeAdditionalStaticField(Object obj, String key) { return removeAdditionalInstanceField(obj.getClass(), key); } public static Object setAdditionalStaticField(Class<?> clazz, String key, Object value) { return setAdditionalInstanceField(clazz, key, value); } public static Object getAdditionalStaticField(Class<?> clazz, String key) { return getAdditionalInstanceField(clazz, key); } public static Object removeAdditionalStaticField(Class<?> clazz, String key) { return removeAdditionalInstanceField(clazz, key); } //################################################################################################# /** * Load an asset from a resource and return the content as byte array. */ public static byte[] assetAsByteArray(Resources res, String path) throws IOException { InputStream is = res.getAssets().open(path); ByteArrayOutputStream buf = new ByteArrayOutputStream(); byte[] temp = new byte[1024]; int read; while ((read = is.read(temp)) > 0) { buf.write(temp, 0, read); } is.close(); return buf.toByteArray(); } /** * Returns the lowercase string representation of the file's MD5 sum. */ public static String getMD5Sum(String file) throws IOException { try { MessageDigest digest = MessageDigest.getInstance("MD5"); InputStream is = new FileInputStream(file); byte[] buffer = new byte[8192]; int read = 0; while ((read = is.read(buffer)) > 0) { digest.update(buffer, 0, read); } is.close(); byte[] md5sum = digest.digest(); BigInteger bigInt = new BigInteger(1, md5sum); return bigInt.toString(16); } catch (NoSuchAlgorithmException e) { return ""; } } //################################################################################################# // TODO helpers for view traversing /*To make it easier, I will try and implement some more helpers: - add view before/after existing view (I already mentioned that I think) - get index of view in its parent - get next/previous sibling (maybe with an optional argument "type", that might be ImageView.class and gives you the next sibling that is an ImageView)? - get next/previous element (similar to the above, but would also work if the next element has a different parent, it would just go up the hierarchy and then down again until it finds a matching element) - find the first child that is an instance of a specified class - find all (direct or indirect) children of a specified class */ }