Skip to content

Commit

Permalink
WEBVIEW: Add another webview - ANDROID only! (#387)
Browse files Browse the repository at this point in the history
Moved the 'webview' script from DemoAndroidLNjScheme into a module of its own; this module is a bit of a stub. Might receive updates to become a nicer looking browser. Meanwhile still intended to showcase how to call Java via jScheme
  • Loading branch information
0-8-15 authored and mgorges committed Nov 27, 2020
1 parent 1cbc09b commit 1e4c9f3
Show file tree
Hide file tree
Showing 6 changed files with 545 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/webview/ANDROID_application_attributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
android:usesCleartextTraffic="true"
157 changes: 157 additions & 0 deletions modules/webview/ANDROID_java_additions
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
class SchemeWebView extends android.webkit.WebView {
LNjScheme.Scheme interpreter = null;
SchemeWebViewClient client = null;

public void ln_log(String msg) {
interpreter.eval(LNjScheme.Scheme.list(LNjScheme.Scheme.sym("log-message"), msg.toCharArray()));
}

private Object iapply(Object fn, Object arg1, Object args) {
return interpreter.eval(LNjScheme.Scheme.cons(fn, LNjScheme.Scheme.cons(arg1, args)));
}

private Object iapply(Object fn, Object arg1) {
return iapply(fn, arg1, null);
}

class SchemeWebViewClient extends android.webkit.WebViewClient {
public Object onloadresource = null;
public Object onpagefinished = null;
public Object onpagecomplete = null;

// LNjScheme.Scheme interpreter = null;
/*
SchemeWebViewClient(LNjScheme.Scheme interp) {
interpreter = interp;
}
*/
public Object eval(Object expr) { return interpreter.eval(expr); }

public void onLoadResource(final android.webkit.WebView view, final String url) {
Object fn = onloadresource;
if(fn!=null) { iapply(fn, view, LNjScheme.Scheme.list(url.toCharArray())); }
}

public void onPageFinished(final android.webkit.WebView view, final String url) {
Object fn = onpagefinished;
if(fn!=null) { iapply(fn, view, LNjScheme.Scheme.list(url.toCharArray())); }
@IF_ANDROIDAPI_GT_22@
if(onpagecomplete!=null) {
view.postVisualStateCallback
(0,
new android.webkit.WebView.VisualStateCallback() {
public void onComplete(long requestId) {
interpreter.eval
(LNjScheme.Scheme.cons
(onpagecomplete,
(LNjScheme.Scheme.list (view, url.toCharArray()))));
}});
}
/* end of IF_ANDROIDAPI_GT_22 */
}

public boolean shouldOverrideUrlLoading(final android.webkit.WebView view, String url) {
return false;
}

//* These suppress the "favicon.ico" request
@Override
public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView view, String url) {
if(url.toLowerCase().contains("/favicon.ico")) {
return new android.webkit.WebResourceResponse("image/png", null, null);
}
return null;
}

@IF_ANDROIDAPI_GT_22@
@Override
public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView view, android.webkit.WebResourceRequest request) {
if(!request.isForMainFrame() && request.getUrl().getPath().endsWith("/favicon.ico")) {
return new android.webkit.WebResourceResponse("image/png", null, null);
}
return null;
}
/* end of IF_ANDROIDAPI_GT_22 */
// end of suppressing the "favicon.ico" request */
}

public SchemeWebView(android.content.Context context, LNjScheme.Scheme interp) {
super(context);
interpreter = interp;
client = new SchemeWebViewClient();
String http_proxy=java.lang.System.getenv("http_proxy");
String https_proxy=java.lang.System.getenv("https_proxy");
if(http_proxy!=null || https_proxy!=null) {
try {
ln_log("webview setting proxy to " + http_proxy /* + " and " + https_proxy*/);
int i = http_proxy.indexOf(':', 7);
String host = http_proxy.substring(7, i);
int port = Integer.parseInt(http_proxy.substring(i+1, http_proxy.length()));
if(!ProxySettings.setProxy(context, host, port)) {
ln_log("webview setting proxy FAILED");
}
/*
androidx.webkit.ProxyConfig.Builder pcb = new androidx.webkit.ProxyConfig.Builder();
if(http_proxy!=null) { pcb.addProxyRule(http_proxy); }
if(https_proxy!=null) { pcb.addProxyRule(https_proxy); }
// pcb.addDirect(); // if desired as fallback
*/
} catch (Exception e) {
ln_log("Setting proxy failed: " + e);
}
}
setWebViewClient(client);
}

public Object SchemeSetProxy(Object args) {
return true; //NYI
}

public Object apply(LNjScheme.Scheme interpreter, Object args) {
Object key0 = LNjScheme.Scheme.first(args);
String key = null;
if(key0 instanceof String) { key = (String)key0; }
if(key == null) {
return LNjScheme.Scheme.error("webview: dispatch key missing");
} else if( key == "load" ) {
setVisibility(android.view.View.VISIBLE);
Object a1 = LNjScheme.Scheme.second(args);
if(a1 instanceof char[]) { loadUrl(new String((char[])a1)); return true; }
else { return LNjScheme.Scheme.error("webview: not a URL " + a1); }
} else if( key == "redraw" ) {
ln_log("webview redraw");
setVisibility(android.view.View.VISIBLE); //onResume();// onDraw(); // that might have to be something else!
onPause();
onResume();
return true;
} else if( key == "setproxy" ) {
return SchemeSetProxy(LNjScheme.Scheme.rest(args));
} else if( key == "onloadresource" ) {
client.onloadresource = LNjScheme.Scheme.second(args);
return true;
} else if( key=="onpagecomplete" ) {
client.onpagecomplete = LNjScheme.Scheme.second(args);
return true;
} else if( key=="onpagefinished" ) {
client.onpagefinished = LNjScheme.Scheme.second(args);
return true;
} else {
return LNjScheme.Scheme.error("webview: unknown key: " + key);
}
}

private static LNMethod lnmethod = new LNMethod("webview!") {
public Object apply(LNjScheme.Scheme interpreter, Object args) {
if(args instanceof LNjScheme.Pair) {
Object a1 = LNjScheme.Scheme.first(args);
if(a1 instanceof SchemeWebView) {
SchemeWebView obj = (SchemeWebView)a1;
return obj.apply(obj.interpreter, LNjScheme.Scheme.rest(args));
} else {
return LNjScheme.Scheme.error("webview: not a webview " + a1);
}
} else { return LNjScheme.Scheme.error("webview: mising arguments"); }
}};

public static LNMethod proc() { return lnmethod; }
}
42 changes: 42 additions & 0 deletions modules/webview/ANDROID_java_oncreate
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
LNjSchemeEvaluate
(LNjScheme.Scheme.cons
(LNjScheme.Scheme.sym("define"),
LNjScheme.Scheme.list
(LNjScheme.Scheme.sym("webview!"), SchemeWebView.proc())));

LNjSchemeEvaluate
(LNjScheme.Scheme.cons
(LNjScheme.Scheme.sym("define"),
LNjScheme.Scheme.list
(LNjScheme.Scheme.sym("make-webview"),
new LNMethod("make-webview") {
public Object apply(LNjScheme.Scheme interpreter, Object args) {
if(args instanceof LNjScheme.Pair) {
Object a1 = null;
a1 = LNjScheme.Scheme.first(args);
if(a1 instanceof android.content.Context) {
return new SchemeWebView((android.content.Context)a1, LNjSchemeSession);
}
}
return LNjScheme.Scheme.error("make-webview" + args);
}}
)));

LNjSchemeEvaluate
// Dummy
(LNjScheme.Scheme.cons
(LNjScheme.Scheme.sym("define"),
LNjScheme.Scheme.list
(LNjScheme.Scheme.sym("webview-set-proxy!"), LNjScheme.Scheme.sym("list"))));

/*
(lnjscheme-eval
`(let ((String (lambda (x) (new "java.lang.String" x)))
(setProperty (method "setProperty" "java.lang.System" "java.lang.String" "java.lang.String")))
(let ((host (String ,(if (= v 0) "" "127.0.0.1")))
(port (String ,(if (= v 0) "" (number->string v)))))
(setProperty (String "http.ProxyHost") host)
(setProperty (String "http.ProxyPort") port)
(setProperty (String "https.ProxyHost") host)
(setProperty (String "https.ProxyPort") port))))
*/
185 changes: 185 additions & 0 deletions modules/webview/ANDROID_java_public_ProxySettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Proxy;
import android.os.Build;
import android.os.Parcelable;
import android.util.ArrayMap;
//import org.apache.http.HttpHost;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* Utility class for setting WebKit proxy used by Android WebView
*/
@SuppressWarnings({"unchecked", "ConstantConditions"})
public class ProxySettings {

private static final String TAG = "ProxySettings";
public static final String LOG_TAG = TAG;

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);
// System.out.println(obj.getClass().getName() + "." + name + " = "+
// out);
return f.get(obj);
}

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, String host, int port) {
boolean ret = false;
setSystemProperties(host, port);

try {
if (Build.VERSION.SDK_INT > 18) {
ret = setKitKatProxy(ctx, host, port);
} else if (Build.VERSION.SDK_INT > 13) {
ret = setICSProxy(host, port);
} else {
/*
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;
*/
return false;
}
}
catch (Exception e) {
e.printStackTrace();
}
return ret;
}

private static boolean setICSProxy(String host, int port) throws
ClassNotFoundException, NoSuchMethodException,
IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException {
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);
m.setAccessible(true);
c.setAccessible(true);
Object properties = c.newInstance(host, port, null);
m.invoke(null, PROXY_CHANGED, properties);
return true;
}
return false;

}

@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean setKitKatProxy(Context context, String host, int port) {
Context appContext = context.getApplicationContext();
try {
Class applictionCls = appContext.getClass();
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 ************* /
final String CLASS_NAME = "android.net.ProxyProperties";
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);
}
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

private static void setSystemProperties(String host, int port) {

java.lang.System.setProperty("http.proxyHost", host);
java.lang.System.setProperty("http.proxyPort", port + "");

java.lang.System.setProperty("https.proxyHost", host);
java.lang.System.setProperty("https.proxyPort", port + "");

}
}
1 change: 1 addition & 0 deletions modules/webview/MODULES
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ln_core ln_jscheme
Loading

0 comments on commit 1e4c9f3

Please sign in to comment.