package org.getlantern.firetweet.proxy;
/**
* Created by todd on 4/24/15.
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.http.HttpHost;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
import android.util.ArrayMap;
import android.net.Proxy;
import java.io.StringWriter;
import java.io.PrintWriter;
import android.content.Intent;
import android.os.Parcelable;
/**
* Utility class for setting WebKit proxy used by Android WebView
*
*/
public class ProxySettings {
private static final String LOG_TAG = "ProxySettings";
static final int PROXY_CHANGED = 193;
private static Object getDeclaredField(Object obj, String name) throws SecurityException,
NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
Object out = f.get(obj);
// System.out.println(obj.getClass().getName() + "." + name + " = "+
// out);
return out;
}
public static Object getRequestQueue(Context ctx) throws Exception {
Object ret = null;
Class networkClass = Class.forName("android.webkit.Network");
if (networkClass != null) {
Object networkObj = invokeMethod(networkClass, "getInstance", new Object[] { ctx },
Context.class);
if (networkObj != null) {
ret = getDeclaredField(networkObj, "mRequestQueue");
}
}
return ret;
}
private static Object invokeMethod(Object object, String methodName, Object[] params,
Class... types) throws Exception {
Object out = null;
Class c = object instanceof Class ? (Class) object : object.getClass();
if (types != null) {
Method method = c.getMethod(methodName, types);
out = method.invoke(object, params);
} else {
Method method = c.getMethod(methodName);
out = method.invoke(object);
}
// System.out.println(object.getClass().getName() + "." + methodName +
// "() = "+ out);
return out;
}
public static void resetProxy(Context ctx) throws Exception {
Object requestQueueObject = getRequestQueue(ctx);
if (requestQueueObject != null) {
setDeclaredField(requestQueueObject, "mProxyHost", null);
}
}
private static void setDeclaredField(Object obj, String name, Object value)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(obj, value);
}
/**
* Override WebKit Proxy settings
*
* @param ctx
* Android ApplicationContext
* @param host
* @param port
* @return true if Proxy was successfully set
*/
public static boolean setProxy(Context ctx, WebView webview,
String host, int port) {
boolean ret = false;
//setSystemProperties(host, port);
try {
if (Build.VERSION.SDK_INT < 14) {
Object requestQueueObject = getRequestQueue(ctx);
if (requestQueueObject != null) {
// Create Proxy config object and set it into request Q
HttpHost httpHost = new HttpHost(host, port, "http");
setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
ret = true;
}
} else if (Build.VERSION.SDK_INT <= 15) {
ret = setICSProxy(ctx, host, port);
} else if (Build.VERSION.SDK_INT <= 18) {
return setProxyJB(webview, host, port);
}
// 4.4 (KK)
else {
return setProxyKK(webview, host, port);
}
} catch (Exception e) {
Log.e(LOG_TAG, "error setting up webkit proxying", e);
}
return ret;
}
private static boolean setICSProxy(Context ctx, String host, int port) throws Exception
{
try
{
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
if (webViewCoreClass != null && proxyPropertiesClass != null)
{
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
Object.class);
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
String.class);
if (m != null && c != null)
{
m.setAccessible(true);
c.setAccessible(true);
Object properties = c.newInstance(host, port, null);
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
m.invoke(null, PROXY_CHANGED, properties);
Log.d("ProxySettings", "Successfully set webview proxy");
return true;
}
else
return false;
}
} catch (Exception e)
{
Log.e("ProxySettings",
"Exception setting WebKit proxy through android.net.ProxyProperties: "
+ e.toString());
} catch (Error e)
{
Log.e("ProxySettings",
"Exception setting WebKit proxy through android.webkit.Network: "
+ e.toString());
}
return false;
}
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
boolean oldAccessibleValue = field.isAccessible();
field.setAccessible(true);
Object result = field.get(classInstance);
field.setAccessible(oldAccessibleValue);
return result;
}
/**
* Set Proxy for Android 4.1 - 4.3.
*/
private static boolean setProxyJB(WebView webview, String host, int port) {
Log.d(LOG_TAG, "Setting proxy with 4.1 - 4.3 API.");
try {
Class wvcClass = Class.forName("android.webkit.WebViewClassic");
Class wvParams[] = new Class[1];
wvParams[0] = Class.forName("android.webkit.WebView");
Method fromWebView = wvcClass.getDeclaredMethod("fromWebView", wvParams);
Object webViewClassic = fromWebView.invoke(null, webview);
Class wv = Class.forName("android.webkit.WebViewClassic");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webViewClassic);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
updateProxyInstance.invoke(sJavaBridge, ppcont.newInstance(host, port, null));
} catch (Exception ex) {
Log.e(LOG_TAG,"Setting proxy with >= 4.1 API failed with error: " + ex.getMessage());
return false;
}
Log.d(LOG_TAG, "Setting proxy with 4.1 - 4.3 API successful!");
return true;
}
// from https://stackoverflow.com/questions/19979578/android-webview-set-proxy-programatically-kitkat
private static boolean setProxyKK(WebView webView, String host, int port) {
Log.d(LOG_TAG, "Setting proxy with >= 4.4 API.");
Context appContext = webView.getContext().getApplicationContext();
try {
Class applictionCls = Class.forName("android.app.Application");
Field loadedApkField = applictionCls.getField("mLoadedApk");
loadedApkField.setAccessible(true);
Object loadedApk = loadedApkField.get(appContext);
Class loadedApkCls = Class.forName("android.app.LoadedApk");
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
receiversField.setAccessible(true);
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
for (Object receiverMap : receivers.values()) {
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
Class clazz = rec.getClass();
if (clazz.getName().contains("ProxyChangeListener")) {
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
/*********** optional, may be need in future *************/
String CLASS_NAME = "android.net.ProxyProperties";
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
CLASS_NAME = "android.net.ProxyProperties";
} else {
CLASS_NAME = "android.net.ProxyInfo";
}
Class cls = Class.forName(CLASS_NAME);
Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
constructor.setAccessible(true);
Object proxyProperties = constructor.newInstance(host, port, null);
intent.putExtra("proxy", (Parcelable) proxyProperties);
/*********** optional, may be need in future *************/
onReceiveMethod.invoke(rec, appContext, intent);
}
}
}
Log.d(LOG_TAG, "Setting proxy with >= 4.4 API successful!");
return true;
} catch (ClassNotFoundException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchFieldException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalAccessException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (IllegalArgumentException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (NoSuchMethodException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (InvocationTargetException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
} catch (InstantiationException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
Log.v(LOG_TAG, e.getMessage());
Log.v(LOG_TAG, exceptionAsString);
}
return false;
}
private static void setSystemProperties(String host, int port) {
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", port + "");
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", port + "");
}
}