All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.robv.android.xposed.ClassLoaderSafeXposedHelper Maven / Gradle / Ivy

Go to download

ratel api,used for developer on ratel system,an extension for xposed framewrok,ratel api compatable with original xposed framework

There is a newer version: 1.3.6
Show newest version
package de.robv.android.xposed;


import android.content.res.AssetManager;
import android.content.res.Resources;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
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.Member;
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.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipFile;

import dalvik.system.DexFile;
import external.org.apache.commons.lang3.ClassUtils;
import external.org.apache.commons.lang3.reflect.MemberUtils;

/**
 * 改造自原始xposed,如果系统中存在xposed框架,那么我们会和系统已存xposed交互。两分xposed的实现会引发classloader不一致冲突。这个工具类可以避免classloader不一致冲突问题
 */
public final class ClassLoaderSafeXposedHelper {
    private ClassLoaderSafeXposedHelper() {
    }

    private static final HashMap> fieldCache = new HashMap<>();
    private static final HashMap> methodCache = new HashMap<>();
    private static final HashMap>> constructorCache = new HashMap<>();

    /**
     * Look up a class with the specified class loader.
     *
     * 

There are various allowed syntaxes for the class name, but it's recommended to use one of * these: *

    *
  • {@code java.lang.String} *
  • {@code java.lang.String[]} (array) *
  • {@code android.app.ActivityThread.ResourcesKey} *
  • {@code android.app.ActivityThread$ResourcesKey} *
* * @param className The class name in one of the formats mentioned above. * @param classLoader The class loader, or {@code null} for the boot class loader. * @return A reference to the class. * @throws ClassNotFoundError In case the class was not found. */ public static Class findClass(String className, ClassLoader classLoader) { if (classLoader == null) classLoader = XposedBridge.BOOTCLASSLOADER; try { return ClassUtils.getClass(classLoader, className, false); } catch (ClassNotFoundException e) { throw new ClassNotFoundError(e); } } /** * Look up and return a class if it exists. * Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist. * * @param className The class name. * @param classLoader The class loader, or {@code null} for the boot class loader. * @return A reference to the class, or {@code null} if it doesn't exist. */ public static Class findClassIfExists(String className, ClassLoader classLoader) { try { return findClass(className, classLoader); } catch (ClassNotFoundError e) { return null; } } /** * Look up a field in a class and set it to accessible. * * @param clazz The class which either declares or inherits the field. * @param fieldName The field name. * @return A reference to the field. * @throws NoSuchFieldError In case the field was not found. */ public static Field findField(Class clazz, String fieldName) { String fullFieldName = clazz.getName() + '#' + fieldName; HashMap classLoaderFieldCache = fieldCache.get(clazz.getClassLoader()); if (classLoaderFieldCache == null) { classLoaderFieldCache = new HashMap<>(); fieldCache.put(clazz.getClassLoader(), classLoaderFieldCache); } if (classLoaderFieldCache.containsKey(fullFieldName)) { Field field = classLoaderFieldCache.get(fullFieldName); if (field == null) throw new NoSuchFieldError(fullFieldName); return field; } try { Field field = findFieldRecursiveImpl(clazz, fieldName); field.setAccessible(true); classLoaderFieldCache.put(fullFieldName, field); return field; } catch (NoSuchFieldException e) { classLoaderFieldCache.put(fullFieldName, null); throw new NoSuchFieldError(fullFieldName); } } /** * Look up and return a field if it exists. * Like {@link #findField}, but doesn't throw an exception if the field doesn't exist. * * @param clazz The class which either declares or inherits the field. * @param fieldName The field name. * @return A reference to the field, or {@code null} if it doesn't exist. */ public static Field findFieldIfExists(Class clazz, String fieldName) { try { return findField(clazz, fieldName); } catch (NoSuchFieldError e) { return null; } } 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. * * @param clazz The class which either declares or inherits the field. * @param type The type of the field. * @return A reference to the first field of the given type. * @throws NoSuchFieldError In case no matching field was not found. */ 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 and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} * for details. */ 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, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback)); return XposedBridge.hookMethod(m, callback); } /** * Look up a method and hook it. The last argument must be the callback for the hook. * *

This combines calls to {@link #findMethodExact(Class, String, Object...)} and * {@link XposedBridge#hookMethod}. * *

The method must be declared or overridden in the given class, inherited * methods are not considered! That's because each method implementation exists only once in * the memory, and when classes inherit it, they just get another reference to the implementation. * Hooking a method therefore applies to all classes inheriting the same implementation. You * have to expect that the hook applies to subclasses (unless they override the method), but you * shouldn't have to worry about hooks applying to superclasses, hence this "limitation". * There could be undesired or even dangerous hooks otherwise, e.g. if you hook * {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs, * making you hook {@code Object.equals()} instead. * *

There are two ways to specify the parameter types. If you already have a reference to the * {@link Class}, use that. For Android framework classes, you can often use something like * {@code String.class}. If you don't have the class reference, you can simply use the * full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}. * It will be passed to {@link #findClass} with the same class loader that is used for the target * method, see its documentation for the allowed notations. * *

Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended) * or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to * {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when * the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters * though, so check the method signature in detail. * *

As last argument to this method (after the list of target method parameters), you need * to specify the callback that should be executed when the method is invoked. It's usually * an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}. * *

Example *

     * // In order to hook this method ...
     * package com.example;
     * public class SomeClass {
     *   public int doSomething(String s, int i, MyClass m) {
     *     ...
     *   }
     * }
     *
     * // ... you can use this call:
     * findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
     *   @Override
     *   protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
     *     String oldText = (String) param.args[0];
     *     Log.d("MyModule", oldText);
     *
     *     param.args[0] = "test";
     *     param.args[1] = 42; // auto-boxing is working here
     *     setBooleanField(param.args[2], "great", true);
     *
     *     // This would not work (as MyClass can't be resolved at compile time):
     *     //   MyClass myClass = (MyClass) param.args[2];
     *     //   myClass.great = true;
     *   }
     * });
     * 
* * @param className The name of the class which implements the method. * @param classLoader The class loader for resolving the target and parameter classes. * @param methodName The target method name. * @param parameterTypesAndCallback The parameter types of the target method, plus the callback. * @return An object which can be used to remove the callback again. * @throws NoSuchMethodError In case the method was not found. * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved. */ 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. * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details. */ public static Method findMethodExact(Class clazz, String methodName, Object... parameterTypes) { return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes)); } /** * Look up and return a method if it exists. * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details. */ public static Method findMethodExactIfExists(Class clazz, String methodName, Object... parameterTypes) { try { return findMethodExact(clazz, methodName, parameterTypes); } catch (ClassNotFoundError | NoSuchMethodError e) { return null; } } /** * Look up a method in a class and set it to accessible. * The method must be declared or overridden in the given class. * *

See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about * the method and parameter type resolution. * * @param className The name of the class which implements the method. * @param classLoader The class loader for resolving the target and parameter classes. * @param methodName The target method name. * @param parameterTypes The parameter types of the target method. * @return A reference to the method. * @throws NoSuchMethodError In case the method was not found. * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved. */ public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) { return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes)); } /** * Look up and return a method if it exists. * Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an * exception if the method doesn't exist. * * @param className The name of the class which implements the method. * @param classLoader The class loader for resolving the target and parameter classes. * @param methodName The target method name. * @param parameterTypes The parameter types of the target method. * @return A reference to the method, or {@code null} if it doesn't exist. */ public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) { try { return findMethodExact(className, classLoader, methodName, parameterTypes); } catch (ClassNotFoundError | NoSuchMethodError e) { return null; } } /** * Look up a method in a class and set it to accessible. * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details. * *

This variant requires that you already have reference to all the parameter types. */ public synchronized static Method findMethodExact(Class clazz, String methodName, Class... parameterTypes) { String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact"; HashMap classLoaderMethodCache = methodCache.get(clazz.getClassLoader()); if (classLoaderMethodCache == null) { classLoaderMethodCache = new HashMap<>(); methodCache.put(clazz.getClassLoader(), classLoaderMethodCache); } if (classLoaderMethodCache.containsKey(fullMethodName)) { Method method = classLoaderMethodCache.get(fullMethodName); if (method == null) throw new NoSuchMethodError(fullMethodName); return method; } try { Method method = clazz.getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); classLoaderMethodCache.put(fullMethodName, method); return method; } catch (NoSuchMethodException e) { classLoaderMethodCache.put(fullMethodName, null); throw new NoSuchMethodError(fullMethodName); } } /** * Returns an array of all methods declared/overridden 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. * * @param clazz The class to look in. * @param returnType The return type, or {@code null} (see above). * @param parameterTypes The parameter types. * @return An array with matching methods, all set to accessible already. */ public static Method[] findMethodsByExactParameters(Class clazz, Class returnType, Class... parameterTypes) { List result = new LinkedList<>(); 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. * *

This does'nt only look for exact matches, but for the best match. All considered candidates * must be compatible with the given parameter types, i.e. the parameters must be assignable * to the method's formal parameters. Inherited methods are considered here. * * @param clazz The class which declares, inherits or overrides the method. * @param methodName The method name. * @param parameterTypes The types of the method's parameters. * @return A reference to the best-matching method. * @throws NoSuchMethodError In case no suitable method was found. */ public synchronized static Method findMethodBestMatch(Class clazz, String methodName, Class... parameterTypes) { String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch"; HashMap classLoaderMethodCache = methodCache.get(clazz.getClassLoader()); if (classLoaderMethodCache == null) { classLoaderMethodCache = new HashMap<>(); methodCache.put(clazz.getClassLoader(), classLoaderMethodCache); } if (classLoaderMethodCache.containsKey(fullMethodName)) { Method method = classLoaderMethodCache.get(fullMethodName); if (method == null) throw new NoSuchMethodError(fullMethodName); return method; } try { Method method = findMethodExact(clazz, methodName, parameterTypes); classLoaderMethodCache.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); classLoaderMethodCache.put(fullMethodName, bestMatch); return bestMatch; } else { NoSuchMethodError e = new NoSuchMethodError(fullMethodName); classLoaderMethodCache.put(fullMethodName, null); throw e; } } /** * Look up a method in a class and set it to accessible. * *

See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant * determines the parameter types from the classes of the given objects. */ 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. * *

See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant * determines the parameter types from the classes of the given objects. For any item that is * {@code null}, the type is taken from {@code parameterTypes} instead. */ 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); } /** * Returns 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; } /** * Retrieve classes from an array, where each element might either be a Class * already, or a String with the full class name. */ private static Class[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) { Class[] parameterClasses = null; for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) { Object type = parameterTypesAndCallback[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, classLoader); 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 parameterClasses; } /** * Returns an array of the given classes. */ 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(); } /** * Look up a constructor of a class and set it to accessible. * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details. */ public static Constructor findConstructorExact(Class clazz, Object... parameterTypes) { return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes)); } /** * Look up and return a constructor if it exists. * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details. */ public static Constructor findConstructorExactIfExists(Class clazz, Object... parameterTypes) { try { return findConstructorExact(clazz, parameterTypes); } catch (ClassNotFoundError | NoSuchMethodError e) { return null; } } /** * Look up a constructor of a class and set it to accessible. * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details. */ public static Constructor findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) { return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes)); } /** * Look up and return a constructor if it exists. * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details. */ public static Constructor findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) { try { return findConstructorExact(className, classLoader, parameterTypes); } catch (ClassNotFoundError | NoSuchMethodError e) { return null; } } /** * Look up a constructor of a class and set it to accessible. * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details. */ public static Constructor findConstructorExact(Class clazz, Class... parameterTypes) { String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact"; HashMap> classLoaderConstructorCache = constructorCache.get(clazz.getClassLoader()); if (classLoaderConstructorCache == null) { classLoaderConstructorCache = new HashMap<>(); constructorCache.put(clazz.getClassLoader(), classLoaderConstructorCache); } if (classLoaderConstructorCache.containsKey(fullConstructorName)) { Constructor constructor = classLoaderConstructorCache.get(fullConstructorName); if (constructor == null) throw new NoSuchMethodError(fullConstructorName); return constructor; } try { Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); classLoaderConstructorCache.put(fullConstructorName, constructor); return constructor; } catch (NoSuchMethodException e) { classLoaderConstructorCache.put(fullConstructorName, null); throw new NoSuchMethodError(fullConstructorName); } } /** * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} * for details. */ public static XC_MethodHook.Unhook findAndHookConstructor(Class clazz, 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]; Constructor m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback)); return XposedBridge.hookMethod(m, callback); } /** * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} * for details. */ public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) { return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback); } /** * Look up a constructor in a class and set it to accessible. * *

See {@link #findMethodBestMatch(Class, String, Class...)} for details. */ public static Constructor findConstructorBestMatch(Class clazz, Class... parameterTypes) { String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch"; HashMap> classLoaderConstructorCache = constructorCache.get(clazz.getClassLoader()); if (classLoaderConstructorCache == null) { classLoaderConstructorCache = new HashMap<>(); constructorCache.put(clazz.getClassLoader(), classLoaderConstructorCache); } if (classLoaderConstructorCache.containsKey(fullConstructorName)) { Constructor constructor = classLoaderConstructorCache.get(fullConstructorName); if (constructor == null) throw new NoSuchMethodError(fullConstructorName); return constructor; } try { Constructor constructor = findConstructorExact(clazz, parameterTypes); classLoaderConstructorCache.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); classLoaderConstructorCache.put(fullConstructorName, bestMatch); return bestMatch; } else { NoSuchMethodError e = new NoSuchMethodError(fullConstructorName); classLoaderConstructorCache.put(fullConstructorName, null); throw e; } } /** * Look up a constructor in a class and set it to accessible. * *

See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant * determines the parameter types from the classes of the given objects. */ public static Constructor findConstructorBestMatch(Class clazz, Object... args) { return findConstructorBestMatch(clazz, getParameterTypes(args)); } /** * Look up a constructor in a class and set it to accessible. * *

See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant * determines the parameter types from the classes of the given objects. For any item that is * {@code null}, the type is taken from {@code parameterTypes} instead. */ 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); } /** * Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException}, * callers are not forced to explicitly catch this. If uncaught, the error will be passed to the * next caller in the stack. */ public static final class ClassNotFoundError extends Error { private static final long serialVersionUID = -1070936889459514628L; /** * @hide */ public ClassNotFoundError(Throwable cause) { super(cause); } /** * @hide */ public ClassNotFoundError(String detailMessage, Throwable cause) { super(detailMessage, cause); } } /** * Returns the index of the first parameter declared with the given type. * * @throws NoSuchFieldError if there is no parameter with that type. * @hide */ public static int getFirstParameterIndexByType(Member method, Class type) { Class[] classes = (method instanceof Method) ? ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes(); for (int i = 0; i < classes.length; i++) { if (classes[i] == type) { return i; } } throw new NoSuchFieldError("No parameter of type " + type + " found in " + method); } /** * Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter. * * @throws NoSuchFieldError if there is no or more than one parameter with that type. * @hide */ public static int getParameterIndexByType(Member method, Class type) { Class[] classes = (method instanceof Method) ? ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes(); int idx = -1; for (int i = 0; i < classes.length; i++) { if (classes[i] == type) { if (idx == -1) { idx = i; } else { throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method); } } } if (idx != -1) { return idx; } else { throw new NoSuchFieldError("No parameter of type " + type + " found in " + method); } } //################################################################################################# /** * Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setObjectField(Object obj, String fieldName, Object value) { try { findField(obj.getClass(), fieldName).set(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setBooleanField(Object obj, String fieldName, boolean value) { try { findField(obj.getClass(), fieldName).setBoolean(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setByteField(Object obj, String fieldName, byte value) { try { findField(obj.getClass(), fieldName).setByte(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setCharField(Object obj, String fieldName, char value) { try { findField(obj.getClass(), fieldName).setChar(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setDoubleField(Object obj, String fieldName, double value) { try { findField(obj.getClass(), fieldName).setDouble(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setFloatField(Object obj, String fieldName, float value) { try { findField(obj.getClass(), fieldName).setFloat(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setIntField(Object obj, String fieldName, int value) { try { findField(obj.getClass(), fieldName).setInt(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setLongField(Object obj, String fieldName, long value) { try { findField(obj.getClass(), fieldName).setLong(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static void setShortField(Object obj, String fieldName, short value) { try { findField(obj.getClass(), fieldName).setShort(obj, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# /** * Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static Object getObjectField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).get(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class. */ public static Object getSurroundingThis(Object obj) { return getObjectField(obj, "this$0"); } /** * Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean getBooleanField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getBoolean(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static byte getByteField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getByte(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static char getCharField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getChar(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static double getDoubleField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getDouble(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static float getFloatField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getFloat(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static int getIntField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getInt(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static long getLongField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getLong(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */ public static short getShortField(Object obj, String fieldName) { try { return findField(obj.getClass(), fieldName).getShort(obj); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# /** * Sets the value of a static object field in the given class. See also {@link #findField}. */ public static void setStaticObjectField(Class clazz, String fieldName, Object value) { try { findField(clazz, fieldName).set(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}. */ public static void setStaticBooleanField(Class clazz, String fieldName, boolean value) { try { findField(clazz, fieldName).setBoolean(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */ public static void setStaticByteField(Class clazz, String fieldName, byte value) { try { findField(clazz, fieldName).setByte(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */ public static void setStaticCharField(Class clazz, String fieldName, char value) { try { findField(clazz, fieldName).setChar(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */ public static void setStaticDoubleField(Class clazz, String fieldName, double value) { try { findField(clazz, fieldName).setDouble(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */ public static void setStaticFloatField(Class clazz, String fieldName, float value) { try { findField(clazz, fieldName).setFloat(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */ public static void setStaticIntField(Class clazz, String fieldName, int value) { try { findField(clazz, fieldName).setInt(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */ public static void setStaticLongField(Class clazz, String fieldName, long value) { try { findField(clazz, fieldName).setLong(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */ public static void setStaticShortField(Class clazz, String fieldName, short value) { try { findField(clazz, fieldName).setShort(null, value); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# /** * Returns the value of a static object field in the given class. See also {@link #findField}. */ public static Object getStaticObjectField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).get(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}. */ public static boolean getStaticBooleanField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getBoolean(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */ public static byte getStaticByteField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getByte(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */ public static char getStaticCharField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getChar(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */ public static double getStaticDoubleField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getDouble(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */ public static float getStaticFloatField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getFloat(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */ public static int getStaticIntField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getInt(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */ public static long getStaticLongField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getLong(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } /** * Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */ public static short getStaticShortField(Class clazz, String fieldName) { try { return findField(clazz, fieldName).getShort(null); } catch (IllegalAccessException e) { // should not happen XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } } //################################################################################################# /** * Calls an instance or static method of the given object. * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}. * * @param obj The object instance. A class reference is not sufficient! * @param methodName The method name. * @param args The arguments for the method call. * @throws NoSuchMethodError In case no suitable method was found. * @throws InvocationTargetError In case an exception was thrown by the invoked method. */ 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 XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Calls an instance or static method of the given object. * See {@link #callMethod(Object, String, Object...)}. * *

This variant allows you to specify parameter types, which can help in case there are multiple * methods with the same name, especially if you call it with {@code null} parameters. */ 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 XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Calls a static method of the given class. * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}. * * @param clazz The class reference. * @param methodName The method name. * @param args The arguments for the method call. * @throws NoSuchMethodError In case no suitable method was found. * @throws InvocationTargetError In case an exception was thrown by the invoked method. */ 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 XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * Calls a static method of the given class. * See {@link #callStaticMethod(Class, String, Object...)}. * *

This variant allows you to specify parameter types, which can help in case there are multiple * methods with the same name, especially if you call it with {@code null} parameters. */ 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 XposedBridge.log(e); throw new IllegalAccessError(e.getMessage()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new InvocationTargetError(e.getCause()); } } /** * This class provides a wrapper for an exception thrown by a method invocation. * * @see #callMethod(Object, String, Object...) * @see #callStaticMethod(Class, String, Object...) * @see #newInstance(Class, Object...) */ public static final class InvocationTargetError extends Error { private static final long serialVersionUID = -1070936889459514628L; /** * @hide */ public InvocationTargetError(Throwable cause) { super(cause); } } //################################################################################################# /** * Creates a new instance of the given class. * The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}. * * @param clazz The class reference. * @param args The arguments for the constructor call. * @throws NoSuchMethodError In case no suitable constructor was found. * @throws InvocationTargetError In case an exception was thrown by the invoked method. * @throws InstantiationError In case the class cannot be instantiated. */ public static Object newInstance(Class clazz, Object... args) { try { return findConstructorBestMatch(clazz, args).newInstance(args); } catch (IllegalAccessException e) { // should not happen XposedBridge.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()); } } /** * Creates a new instance of the given class. * See {@link #newInstance(Class, Object...)}. * *

This variant allows you to specify parameter types, which can help in case there are multiple * constructors with the same name, especially if you call it with {@code null} parameters. */ public static Object newInstance(Class clazz, Class[] parameterTypes, Object... args) { try { return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args); } catch (IllegalAccessException e) { // should not happen XposedBridge.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()); } } //################################################################################################# //################################################################################################# /** * Loads an asset from a resource object and returns the content as {@code byte} array. * * @param res The resources from which the asset should be loaded. * @param path The path to the asset, as in {@link AssetManager#open}. * @return The content of the asset. */ public static byte[] assetAsByteArray(Resources res, String path) throws IOException { return inputStreamToByteArray(res.getAssets().open(path)); } /*package*/ static byte[] inputStreamToByteArray(InputStream is) throws IOException { 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(); } /** * Invokes the {@link Closeable#close()} method, ignoring IOExceptions. */ /*package*/ static void closeSilently(Closeable c) { if (c != null) { try { c.close(); } catch (IOException ignored) { } } } /** * Invokes the {@link DexFile#close()} method, ignoring IOExceptions. */ /*package*/ static void closeSilently(DexFile dexFile) { if (dexFile != null) { try { dexFile.close(); } catch (IOException ignored) { } } } /** * Invokes the {@link ZipFile#close()} method, ignoring IOExceptions. */ /*package*/ static void closeSilently(ZipFile zipFile) { if (zipFile != null) { try { zipFile.close(); } catch (IOException ignored) { } } } /** * Returns the lowercase hex string representation of a file's MD5 hash 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; 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 ""; } } //################################################################################################# /*package*/ static boolean fileContains(File file, String str) throws IOException { // There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep), // but the naive approach should be sufficient here. BufferedReader in = null; try { in = new BufferedReader(new FileReader(file)); String line; while ((line = in.readLine()) != null) { if (line.contains(str)) { return true; } } return false; } finally { closeSilently(in); } } //################################################################################################# /** * Returns the method that is overridden by the given method. * It returns {@code null} if the method doesn't override another method or if that method is * abstract, i.e. if this is the first implementation in the hierarchy. */ /*package*/ static Method getOverriddenMethod(Method method) { int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) { return null; } String name = method.getName(); Class[] parameters = method.getParameterTypes(); Class clazz = method.getDeclaringClass().getSuperclass(); while (clazz != null) { try { Method superMethod = clazz.getDeclaredMethod(name, parameters); modifiers = superMethod.getModifiers(); if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) { return superMethod; } else { return null; } } catch (NoSuchMethodException ignored) { clazz = clazz.getSuperclass(); } } return null; } /** * Returns all methods which this class overrides. */ /*package*/ static Set getOverriddenMethods(Class clazz) { Set methods = new HashSet<>(); for (Method method : clazz.getDeclaredMethods()) { Method overridden = getOverriddenMethod(method); if (overridden != null) { methods.add(overridden); } } return methods; } //################################################################################################# // 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 */ }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy