package triaina.webview;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import android.webkit.JavascriptInterface;
import triaina.commons.exception.InvocationRuntimeException;
import triaina.commons.exception.JSONConvertException;
import triaina.commons.exception.NotFoundRuntimeException;
import triaina.commons.json.JSONConverter;
import triaina.commons.utils.ClassUtils;
import triaina.commons.utils.FloatUtils;
import triaina.commons.utils.JSONObjectUtils;
import triaina.webview.bridge.BridgeLifecyclable;
import triaina.webview.config.BridgeMethodConfig;
import triaina.webview.config.BridgeObjectConfig;
import triaina.webview.entity.Error;
import triaina.webview.entity.Params;
import triaina.webview.entity.Result;
import org.json.JSONObject;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
public class DeviceBridgeProxy {
private static final String TAG = DeviceBridgeProxy.class.getSimpleName();
private CallbackHelper mCallbackHelper = new CallbackHelper();
private WebViewBridge mWebViewBridge;
private Handler mHandler;
private BridgeObjectConfig mConfigSet = new BridgeObjectConfig();
private Map<String, Object> mReceiverMap = new HashMap<String, Object>();
public DeviceBridgeProxy(WebViewBridge webViewBridge, Handler handler) {
mWebViewBridge = webViewBridge;
mHandler = handler;
}
public void addBridgeObjectConfig(Object bridgeObject, BridgeObjectConfig config) {
mConfigSet.add(config);
Collection<BridgeMethodConfig> methodConfigs = config.getMethodConfigs();
for (BridgeMethodConfig methodConfig : methodConfigs)
mReceiverMap.put(methodConfig.getDest(), bridgeObject);
}
public BridgeObjectConfig getBridgeConfigSet() {
return mConfigSet;
}
public void resume() {
Collection<Object> bridges = mReceiverMap.values();
for (Object bridge : bridges) {
if (bridge instanceof BridgeLifecyclable)
((BridgeLifecyclable)bridge).onResume();
}
}
public void pause() {
Collection<Object> bridges = mReceiverMap.values();
for (Object bridge : bridges) {
if (bridge instanceof BridgeLifecyclable)
((BridgeLifecyclable)bridge).onPause();
}
}
public void destroy() {
Collection<Object> bridges = mReceiverMap.values();
for (Object bridge : bridges) {
try {
if (bridge instanceof BridgeLifecyclable)
((BridgeLifecyclable)bridge).onDestroy();
} catch (Exception exp) {
Log.w(TAG, exp.getMessage() + "", exp);
}
}
}
@JavascriptInterface
public void notifyToDevice(String data) {
final String jsonText = decode(data);
logging("notified", jsonText);
mHandler.post(new Runnable() {
@Override
public void run() {
String id = null;
String dest = null;
try {
final JSONObject json = JSONObjectUtils.parse(jsonText);
validateParamsVersion(json);
if (json.has("params")) {
if (json.has("id"))
id = json.optString("id");
dest = JSONObjectUtils.getString(json, "dest");
invoke(id, dest, JSONObjectUtils.getJSONObject(json, "params"));
} else {
id = JSONObjectUtils.getString(json, "id");
@SuppressWarnings("unchecked")
Callback<Result> callback = (Callback<Result>) mWebViewBridge.getCallback(id);
if (callback == null)
return;
mWebViewBridge.removeCallback(id);
if (json.has("result"))
mCallbackHelper.invokeSucceed(mWebViewBridge, callback,
JSONObjectUtils.getJSONObject(json, "result"));
else if (json.has("error"))
mCallbackHelper.invokeFail(mWebViewBridge, callback,
JSONObjectUtils.getJSONObject(json, "error"));
}
} catch (NotFoundRuntimeException exp) {
Log.w(TAG, exp.getMessage() + "", exp);
mWebViewBridge.returnToWeb(id, dest,
new Error(ErrorCode.NOT_FOUND_BRIDGE_ERROR.getCode(), exp.getMessage() + "", dest));
} catch (JSONConvertException exp) {
Log.e(TAG, exp.getMessage() + "", exp);
mWebViewBridge.returnToWeb(id, dest,
new Error(ErrorCode.JSON_PARSE_ERROR.getCode(), exp.getMessage() + "", dest));
} catch (InvocationTargetException exp) {
Log.e(TAG, exp.getMessage() + "", exp);
mWebViewBridge.returnToWeb(id, dest,
new Error(ErrorCode.INVOCATION_BRIDGE_ERROR.getCode(), exp.getMessage() + "", dest));
} catch (Exception exp) {
Log.e(TAG, exp.getMessage() + "", exp);
mWebViewBridge.returnToWeb(id, dest, new Error(ErrorCode.UNKNOWN_ERROR.getCode(), exp.getMessage()
+ "", dest));
}
}
});
}
private String decode(String data) {
try {
return URLDecoder.decode(data, "UTF-8");
} catch (UnsupportedEncodingException exp) {
Log.e(TAG, exp.getMessage() + "", exp);
}
return null;
}
private void invoke(String id, String dest, JSONObject json) throws InvocationTargetException,
IllegalArgumentException, IllegalAccessException, JSONConvertException {
BridgeMethodConfig config = mConfigSet.get(dest);
if (config == null)
throw new NotFoundRuntimeException("cannot find " + dest + " bridge method");
final Method method = config.getMethod();
final Class<?>[] argTypes = method.getParameterTypes();
Params params = null;
Callback<?> callback = null;
if (argTypes.length > 0 && ClassUtils.isImplement(argTypes[0], Params.class))
params = (Params) JSONConverter.toObject(json, argTypes[0]);
if (TextUtils.isEmpty(id))
callback = new DummyCallback(dest);
else
callback = new WebViewBridgeCallback(id, dest);
// Object receiver = config.getReceiver();
Object receiver = mReceiverMap.get(dest);
if (argTypes.length == 2)
method.invoke(receiver, params, callback);
else if (argTypes.length == 1) {
if (Callback.class.equals(argTypes[0]))
method.invoke(receiver, callback);
else {
method.invoke(receiver, params);
callback.succeed(mWebViewBridge, null);
}
} else
method.invoke(receiver);
}
protected void validateParamsVersion(JSONObject json) {
double version = Math.floor(FloatUtils.parse(JSONObjectUtils.getString(json, "bridge")));
if (version != WebViewBridge.COMPATIBLE_VERSION) {
throw new InvocationRuntimeException("Need WebViewBridge newer than "
+ (WebViewBridge.COMPATIBLE_VERSION + 1));
}
}
protected void logging(String event, String jsonText) {
String arg = jsonText == null ? "null" : jsonText;
Log.d(TAG, event + " from Web with " + arg);
}
/*
* private Method findMethod(String dest) { Method method =
* mConfig.getMethodMap().get(dest); if (method == null) throw new
* NotFoundRuntimeException("cannot find " + dest + " bridge method");
* return method; }
*/
public static class WebViewBridgeCallback implements Callback<Result> {
private String mId;
private String mDest;
public WebViewBridgeCallback(String id, String dest) {
mId = id;
mDest = dest;
}
public WebViewBridgeCallback(Parcel source) {
mId = source.readString();
mDest = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mDest);
}
@Override
public void succeed(final WebViewBridge bridge, final Result result) {
bridge.returnToWeb(mId, mDest, result);
}
@Override
public void fail(final WebViewBridge bridge, final String code, final String msg) {
bridge.returnToWeb(mId, mDest, new Error(code, msg, mDest));
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<WebViewBridgeCallback> CREATOR = new Parcelable.Creator<WebViewBridgeCallback>() {
@Override
public WebViewBridgeCallback createFromParcel(Parcel source) {
return new WebViewBridgeCallback(source);
}
@Override
public WebViewBridgeCallback[] newArray(int size) {
return new WebViewBridgeCallback[size];
}
};
}
public static class DummyCallback implements Callback<Result> {
private static final String TAG = "DummyCallback";
private String mDest;
public DummyCallback(String dest) {
mDest = dest;
}
public DummyCallback(Parcel source) {
mDest = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mDest);
}
@Override
public void succeed(WebViewBridge bridge, Result result) {
Log.w(TAG, "unnecessary callback is called with " + mDest);
}
@Override
public void fail(WebViewBridge bridge, String code, String msg) {
Log.w(TAG, "unnecessary callback is called with " + mDest);
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<DummyCallback> CREATOR = new Parcelable.Creator<DummyCallback>() {
@Override
public DummyCallback createFromParcel(Parcel source) {
return new DummyCallback(source);
}
@Override
public DummyCallback[] newArray(int size) {
return new DummyCallback[size];
}
};
}
}