package cm.android.hook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.text.TextUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* <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 Logger logger = LoggerFactory.getLogger("MethodInvocationStub");
private Map<String, MethodProxy> mInternalMethodProxies = new HashMap<>();
private T mBaseInterface;
private T mProxyInterface;
private String mIdentityName;
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 {
logger.debug("Unable to build HookDelegate: {}.", getIdentityName());
}
}
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())) {
logger.warn("The Hook({}, {}) 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());
try {
if (methodProxy != null && methodProxy.isEnable()) {
if (methodProxy.beforeCall(mBaseInterface, method, args)) {
Object res = methodProxy.call(mBaseInterface, method, args);
res = methodProxy.afterCall(mBaseInterface, method, args, res);
return res;
}
}
return method.invoke(mBaseInterface, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause != null) {
throw cause;
}
throw e;
}
}
}
}