package com.lody.virtual.client.hook.base; import android.text.TextUtils; import android.util.Log; import com.lody.virtual.client.hook.utils.MethodParameterUtils; import com.lody.virtual.helper.utils.VLog; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * @author Lody * <p> * HookHandler uses Java's {@link Proxy} to create a wrapper for existing services. * <p> * When any method is called on the wrapper, it checks if there is any {@link MethodProxy} registered * and enabled for that method. If so, it calls the startUniformer instead of the wrapped implementation. * <p> * The whole thing is managed by a {@link MethodInvocationProxy} subclass */ @SuppressWarnings("unchecked") public class MethodInvocationStub<T> { private static final String TAG = MethodInvocationStub.class.getSimpleName(); private Map<String, MethodProxy> mInternalMethodProxies = new HashMap<>(); private T mBaseInterface; private T mProxyInterface; private String mIdentityName; private LogInvocation.Condition mInvocationLoggingCondition = LogInvocation.Condition.NEVER; public Map<String, MethodProxy> getAllHooks() { return mInternalMethodProxies; } public MethodInvocationStub(T baseInterface, Class<?>... proxyInterfaces) { this.mBaseInterface = baseInterface; if (baseInterface != null) { if (proxyInterfaces == null) { proxyInterfaces = MethodParameterUtils.getAllInterface(baseInterface.getClass()); } mProxyInterface = (T) Proxy.newProxyInstance(baseInterface.getClass().getClassLoader(), proxyInterfaces, new HookInvocationHandler()); } else { VLog.d(TAG, "Unable to build HookDelegate: %s.", getIdentityName()); } } public LogInvocation.Condition getInvocationLoggingCondition() { return mInvocationLoggingCondition; } public void setInvocationLoggingCondition(LogInvocation.Condition invocationLoggingCondition) { mInvocationLoggingCondition = invocationLoggingCondition; } public void setIdentityName(String identityName) { this.mIdentityName = identityName; } public String getIdentityName() { if (mIdentityName != null) { return mIdentityName; } return getClass().getSimpleName(); } public MethodInvocationStub(T baseInterface) { this(baseInterface, (Class[]) null); } /** * Copy all proxies from the input HookDelegate. * * @param from the HookDelegate we copy from. */ public void copyMethodProxies(MethodInvocationStub from) { this.mInternalMethodProxies.putAll(from.getAllHooks()); } /** * Add a method proxy. * * @param methodProxy proxy */ public MethodProxy addMethodProxy(MethodProxy methodProxy) { if (methodProxy != null && !TextUtils.isEmpty(methodProxy.getMethodName())) { if (mInternalMethodProxies.containsKey(methodProxy.getMethodName())) { VLog.w(TAG, "The Hook(%s, %s) you added has been in existence.", methodProxy.getMethodName(), methodProxy.getClass().getName()); return methodProxy; } mInternalMethodProxies.put(methodProxy.getMethodName(), methodProxy); } return methodProxy; } /** * Remove a method proxy. * * @param hookName proxy * @return The proxy you removed */ public MethodProxy removeMethodProxy(String hookName) { return mInternalMethodProxies.remove(hookName); } /** * Remove a method proxy. * * @param methodProxy target proxy */ public void removeMethodProxy(MethodProxy methodProxy) { if (methodProxy != null) { removeMethodProxy(methodProxy.getMethodName()); } } /** * Remove all method proxies. */ public void removeAllMethodProxies() { mInternalMethodProxies.clear(); } /** * Get the startUniformer by its name. * * @param name name of the Hook * @param <H> Type of the Hook * @return target startUniformer */ @SuppressWarnings("unchecked") public <H extends MethodProxy> H getMethodProxy(String name) { return (H) mInternalMethodProxies.get(name); } /** * @return Proxy interface */ public T getProxyInterface() { return mProxyInterface; } /** * @return Origin Interface */ public T getBaseInterface() { return mBaseInterface; } /** * @return count of the hooks */ public int getMethodProxiesCount() { return mInternalMethodProxies.size(); } private class HookInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodProxy methodProxy = getMethodProxy(method.getName()); boolean useProxy = (methodProxy != null && methodProxy.isEnable()); boolean mightLog = (mInvocationLoggingCondition != LogInvocation.Condition.NEVER) || (methodProxy != null && methodProxy.getInvocationLoggingCondition() != LogInvocation.Condition.NEVER); String argStr = null; Object res = null; Throwable exception = null; if (mightLog) { // Arguments to string is done before the method is called because the method might actually change it argStr = Arrays.toString(args); argStr = argStr.substring(1, argStr.length()-1); } try { if (useProxy && methodProxy.beforeCall(mBaseInterface, method, args)) { res = methodProxy.call(mBaseInterface, method, args); res = methodProxy.afterCall(mBaseInterface, method, args, res); } else { res = method.invoke(mBaseInterface, args); } return res; } catch (Throwable t) { exception = t; if (exception instanceof InvocationTargetException && ((InvocationTargetException) exception).getTargetException() != null) { exception = ((InvocationTargetException) exception).getTargetException(); } throw exception; } finally { if (mightLog) { int logPriority = mInvocationLoggingCondition.getLogLevel(useProxy, exception != null); if (methodProxy != null) { logPriority = Math.max(logPriority, methodProxy.getInvocationLoggingCondition().getLogLevel(useProxy, exception != null)); } if (logPriority >= 0) { String retString; if (exception != null) { retString = exception.toString(); } else if (method.getReturnType().equals(void.class)) { retString = "void"; } else { retString = String.valueOf(res); } Log.println(logPriority, TAG, method.getDeclaringClass().getSimpleName() + "." + method.getName() + "(" + argStr + ") => " + retString); } } } } } }