content: /*
   * 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 static com.taobao.android.dexposed.XposedHelpers.getIntField;
  
  import java.lang.reflect.AccessibleObject;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Member;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import android.content.Context;
  import android.os.Build.VERSION;
  import android.util.Log;
  
  import com.taobao.android.dexposed.XC_MethodHook.MethodHookParam;
  import com.taobao.android.dexposed.XC_MethodHook.Unhook;
  import com.taobao.android.dexposed.XC_MethodHook.XC_MethodKeepHook;
  import com.taobao.android.dexposed.XC_MethodReplacement.XC_MethodKeepReplacement;
  import com.taobao.android.dexposed.XposedHelpers.InvocationTargetError;
  
  
  public final class DexposedBridge {
  
  	private static final int RUNTIME_UNKNOW = 0;
  	private static final int RUNTIME_DALVIK = 1;
  	private static final int RUNTIME_ART = 2;
  	private static int runtime = RUNTIME_UNKNOW;
  	
  	private static final Object[] EMPTY_ARRAY = new Object[0];
  	public static final ClassLoader BOOTCLASSLOADER = ClassLoader.getSystemClassLoader();
  
  
  	// built-in handlers
  	private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> hookedMethodCallbacks
  									= new HashMap<Member, CopyOnWriteSortedSet<XC_MethodHook>>();
  	
  	private static final ArrayList<XC_MethodHook.Unhook> allUnhookCallbacks = new ArrayList<XC_MethodHook.Unhook>();
  
  	
  	private static int getRuntime() {
  
  		if(VERSION.SDK_INT >= 21){
  			return RUNTIME_ART;
  		} else if(VERSION.SDK_INT >= 20) {
  			String vm = System.getProperty("persist.sys.dalvik.vm.lib", "");
  			if(vm.contains("art"))
  				return RUNTIME_ART;
  			else
  				return RUNTIME_DALVIK;
  		} else {
  			return RUNTIME_DALVIK;
  		}
  	}
  
  	/**
  	 * Writes a message to BASE_DIR/log/debug.log (needs to have chmod 777)
  	 * @param text log message
  	 */
  	public synchronized static void log(String text) {
  		Log.d("Dexposed", text);
  	}
  
  	/**
  	 * Log the stack trace
  	 * @param t The Throwable object for the stacktrace
  	 * @see DexposedBridge#log(String)
  	 */
  	public synchronized static void log(Throwable t) {
  		log(Log.getStackTraceString(t));
  	}
  
  	/**
  	 * Hook any method with the specified callback
  	 * 
  	 * @param hookMethod The method to be hooked
  	 * @param callback 
  	 */
  	public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
  		if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
  			throw new IllegalArgumentException("only methods and constructors can be hooked");
  		}
  		
  		boolean newMethod = false;
  		CopyOnWriteSortedSet<XC_MethodHook> callbacks;
  		synchronized (hookedMethodCallbacks) {
  			callbacks = hookedMethodCallbacks.get(hookMethod);
  			if (callbacks == null) {
  				callbacks = new CopyOnWriteSortedSet<XC_MethodHook>();
  				hookedMethodCallbacks.put(hookMethod, callbacks);
  				newMethod = true;
  			}
  		}
  		callbacks.add(callback);
  		if (newMethod) {
  			Class<?> declaringClass = hookMethod.getDeclaringClass();
  			if(runtime == RUNTIME_UNKNOW)  runtime = getRuntime();
  			int slot = (runtime == RUNTIME_DALVIK) ? (int) getIntField(hookMethod, "slot") : 0;
  
  			Class<?>[] parameterTypes;
  			Class<?> returnType;
  			if (hookMethod instanceof Method) {
  				parameterTypes = ((Method) hookMethod).getParameterTypes();
  				returnType = ((Method) hookMethod).getReturnType();
  			} else {
  				parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
  				returnType = null;
  			}
  
  			AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
  			hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
  		}
  		return callback.new Unhook(hookMethod);
  	}
  	
  	/** 
  	 * Removes the callback for a hooked method
  	 * @param hookMethod The method for which the callback should be removed
  	 * @param callback The reference to the callback as specified in {@link #hookMethod}
  	 */
  	public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
  		CopyOnWriteSortedSet<XC_MethodHook> callbacks;
  		synchronized (hookedMethodCallbacks) {
  			callbacks = hookedMethodCallbacks.get(hookMethod);
  			if (callbacks == null)
  				return;
  		}	
  		callbacks.remove(callback);
  	}
  
  	public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
  		Set<XC_MethodHook.Unhook> unhooks = new HashSet<XC_MethodHook.Unhook>();
  		for (Member method : hookClass.getDeclaredMethods())
  			if (method.getName().equals(methodName))
  				unhooks.add(hookMethod(method, callback));
  		return unhooks;
  	}
  	
  	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 = XposedHelpers.findMethodExact(clazz, methodName, parameterTypesAndCallback);
  		XC_MethodHook.Unhook unhook = hookMethod(m, callback);
  		if (!(callback instanceof XC_MethodKeepHook
  				|| callback instanceof XC_MethodKeepReplacement)) {
  			synchronized (allUnhookCallbacks) {
  				allUnhookCallbacks.add(unhook);
  			}
  		}
  		return unhook;
  	}
  	
  	public static void unhookAllMethods() {
  		synchronized (allUnhookCallbacks) {
  			for (int i = 0; i < allUnhookCallbacks.size(); i++) {
  				((Unhook) allUnhookCallbacks.get(i)).unhook();
  			}
  			allUnhookCallbacks.clear();
  		}
  	}
  	
  	public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) {
  		Set<XC_MethodHook.Unhook> unhooks = new HashSet<XC_MethodHook.Unhook>();
  		for (Member constructor : hookClass.getDeclaredConstructors())
  			unhooks.add(hookMethod(constructor, callback));
  		return unhooks;
  	}
  	
  	/**
  	 * This method is called as a replacement for hooked methods.
  	 */
  	private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
  			Object thisObject, Object[] args) throws Throwable {
  		AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
  
  		Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
  		final int callbacksLength = callbacksSnapshot.length;
  		if (callbacksLength == 0) {
  			try {
  				return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
  						additionalInfo.returnType, thisObject, args);
  			} catch (InvocationTargetException e) {
  				throw e.getCause();
  			}
  		}
  
  		MethodHookParam param = new MethodHookParam();
  		param.method  = method;
  		param.thisObject = thisObject;
  		param.args = args;
  
  		// call "before method" callbacks
  		int beforeIdx = 0;
  		do {
  			try {
  				((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
  			} catch (Throwable t) {
  				log(t);
  
  				// reset result (ignoring what the unexpectedly exiting callback did)
  				param.setResult(null);
  				param.returnEarly = false;
  				continue;
  			}
  
  			if (param.returnEarly) {
  				// skip remaining "before" callbacks and corresponding "after" callbacks
  				beforeIdx++;
  				break;
  			}
  		} while (++beforeIdx < callbacksLength);
  
  		// call original method if not requested otherwise
  		if (!param.returnEarly) {
  			try {
  				param.setResult(invokeOriginalMethodNative(method, originalMethodId,
  						additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
  			} catch (InvocationTargetException e) {
  				param.setThrowable(e.getCause());
  			}
  		}
  
  		// call "after method" callbacks
  		int afterIdx = beforeIdx - 1;
  		do {
  			Object lastResult =  param.getResult();
  			Throwable lastThrowable = param.getThrowable();
  
  			try {
  				((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
  			} catch (Throwable t) {
  				DexposedBridge.log(t);
  
  				// reset to last result (ignoring what the unexpectedly exiting callback did)
  				if (lastThrowable == null)
  					param.setResult(lastResult);
  				else
  					param.setThrowable(lastThrowable);
  			}
  		} while (--afterIdx >= 0);
  
  		// return
  		if (param.hasThrowable())
  			throw param.getThrowable();
  		else
  			return param.getResult();
  	}
  	
  	/**
  	 * Check device if can run dexposed, and load libs auto.
  	 */
  	public synchronized static boolean canDexposed(Context context) {
  		if (!DeviceCheck.isDeviceSupport(context)) {
  			return false;
  		}
  		//load dexposed lib for hook.
  		return loadDexposedLib(context);
  	}
  	
  	private static boolean loadDexposedLib(Context context) {
  		// load dexposed lib for hook.
  		try {
  			if (android.os.Build.VERSION.SDK_INT == 22){
  				System.loadLibrary("dexposed_l51");
  			} else if (android.os.Build.VERSION.SDK_INT > 19 && android.os.Build.VERSION.SDK_INT <= 21){
  				System.loadLibrary("dexposed_l");
  			} else if (android.os.Build.VERSION.SDK_INT > 14){
  				System.loadLibrary("dexposed");
  			} else {
  				return false;
  			}
  			return true;
  		} catch (Throwable e) {
  			return false;
  		}
  	}
  	
  	private native static Object invokeSuperNative(Object obj, Object[] args, Member method, Class<?> declaringClass,
              Class<?>[] parameterTypes, Class<?> returnType, int slot)
                      throws IllegalAccessException, IllegalArgumentException,
                              InvocationTargetException;
  	
  	public static Object invokeSuper(Object obj, Member method, Object... args) throws NoSuchFieldException {
  		
  		try {
  			if(runtime == RUNTIME_UNKNOW)  runtime = getRuntime();
  
  			int slot = 0;
  			if(runtime == RUNTIME_DALVIK) {
  				//get the super method slot
  				Method m = XposedHelpers.findMethodExact(obj.getClass().getSuperclass(), method.getName(), ((Method) method).getParameterTypes());
  				slot =  (int) getIntField(m, "slot");
  			}
  
  			return invokeSuperNative(obj, args, method, method.getDeclaringClass(), ((Method) method).getParameterTypes(), ((Method) method).getReturnType(), slot);
  
  		} catch (IllegalAccessException e) {
  			throw new IllegalAccessError(e.getMessage());
  		} catch (IllegalArgumentException e) {
  			throw e;
  		} catch (InvocationTargetException e) {
  			throw new InvocationTargetError(e.getCause());
  		}
  	}
  	
  	/**
  	 * Intercept every call to the specified method and call a handler function instead.
  	 * @param method The method to intercept
  	 */
  	private native synchronized static void hookMethodNative(Member method, Class<?> declaringClass, int slot, Object additionalInfo);
  	
  	private native static Object invokeOriginalMethodNative(Member method, int methodId,
  			Class<?>[] parameterTypes, Class<?> returnType, Object thisObject, Object[] args)
  			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
  
  
  	/**
  	 * Basically the same as {@link Method#invoke}, but calls the original method
  	 * as it was before the interception by Xposed. Also, access permissions are not checked.
  	 * 
  	 * @param method Method to be called
  	 * @param thisObject For non-static calls, the "this" pointer
  	 * @param args Arguments for the method call as Object[] array
  	 * @return The result returned from the invoked method
  	 * @throws NullPointerException
  	 *             if {@code receiver == null} for a non-static method
  	 * @throws IllegalAccessException
  	 *             if this method is not accessible (see {@link AccessibleObject})
  	 * @throws IllegalArgumentException
  	 *             if the number of arguments doesn't match the number of parameters, the receiver
  	 *             is incompatible with the declaring class, or an argument could not be unboxed
  	 *             or converted by a widening conversion to the corresponding parameter type
  	 * @throws InvocationTargetException
  	 *             if an exception was thrown by the invoked method
  
  	 */
  	public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
  			throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  		if (args == null) {
  			args = EMPTY_ARRAY;
  		}
  
  		Class<?>[] parameterTypes;
  		Class<?> returnType;
  		if (method instanceof Method) {
  			parameterTypes = ((Method) method).getParameterTypes();
  			returnType = ((Method) method).getReturnType();
  
  		} else if (method instanceof Constructor) {
  			parameterTypes = ((Constructor<?>) method).getParameterTypes();
  			returnType = null;
  		} else {
  			throw new IllegalArgumentException("method must be of type Method or Constructor");
  		}
  
  		return invokeOriginalMethodNative(method, 0, parameterTypes, returnType, thisObject, args);
  	}
  
  	public static class CopyOnWriteSortedSet<E> {
  		private transient volatile Object[] elements = EMPTY_ARRAY;
  
  		public synchronized boolean add(E e) {
  			int index = indexOf(e);
  			if (index >= 0)
  				return false;
  
  			Object[] newElements = new Object[elements.length + 1];
  			System.arraycopy(elements, 0, newElements, 0, elements.length);
  			newElements[elements.length] = e;
  			Arrays.sort(newElements);
  			elements = newElements;
  			return true;
  		}
  
  		public synchronized boolean remove(E e) {
  			int index = indexOf(e);
  			if (index == -1)
  				return false;
  
  			Object[] newElements = new Object[elements.length - 1];
  			System.arraycopy(elements, 0, newElements, 0, index);
  			System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
  			elements = newElements;
  			return true;
  		}
  		
  		public synchronized void clear(){
  			elements = EMPTY_ARRAY;
  		}
  
  		private int indexOf(Object o) {
  			for (int i = 0; i < elements.length; i++) {
  				if (o.equals(elements[i]))
  					return i;
  			}
  			return -1;
  		}
  
  		public Object[] getSnapshot() {
  			return elements;
  		}
  	}
  
  	private static class AdditionalHookInfo {
  		final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
  		final Class<?>[] parameterTypes;
  		final Class<?> returnType;
  		String shorty;
  
  		private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
  			this.callbacks = callbacks;
  			this.parameterTypes = parameterTypes;
  			this.returnType = returnType;
  
  			StringBuilder sb = new StringBuilder(64);
  			sb.append(Class2Shorty(returnType));
  			for(Class<?> c : parameterTypes){
  				sb.append(Class2Shorty(c));
  			}
  
  			shorty = sb.toString();
  		}
  
  		String Class2Shorty(Class<?> cls) {
  			if(cls.isPrimitive()){
  				return builtInMap.get(cls);
  			} else
  				return "L";
  		}
  	}
  
  	private static Map<Class, String> builtInMap = new HashMap<Class, String>(){
  		{
  			put(Integer.TYPE, "I");
  			put(Long.TYPE, "J");
  			put(Double.TYPE, "D" );
  			put(Float.TYPE, "F" );
  			put(Boolean.TYPE, "Z" );
  			put(Character.TYPE, "C" );
  			put(Byte.TYPE, "B" );
  			put(Void.TYPE, "V" );
  			put(Short.TYPE, "S" );
  		}
  	};
  }
  