Skip to content

Commit 1e4c9f3

Browse files
0-8-15mgorges
authored andcommitted
WEBVIEW: Add another webview - ANDROID only! (#387)
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
1 parent 1cbc09b commit 1e4c9f3

File tree

6 files changed

+545
-0
lines changed

6 files changed

+545
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
android:usesCleartextTraffic="true"
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
class SchemeWebView extends android.webkit.WebView {
2+
LNjScheme.Scheme interpreter = null;
3+
SchemeWebViewClient client = null;
4+
5+
public void ln_log(String msg) {
6+
interpreter.eval(LNjScheme.Scheme.list(LNjScheme.Scheme.sym("log-message"), msg.toCharArray()));
7+
}
8+
9+
private Object iapply(Object fn, Object arg1, Object args) {
10+
return interpreter.eval(LNjScheme.Scheme.cons(fn, LNjScheme.Scheme.cons(arg1, args)));
11+
}
12+
13+
private Object iapply(Object fn, Object arg1) {
14+
return iapply(fn, arg1, null);
15+
}
16+
17+
class SchemeWebViewClient extends android.webkit.WebViewClient {
18+
public Object onloadresource = null;
19+
public Object onpagefinished = null;
20+
public Object onpagecomplete = null;
21+
22+
// LNjScheme.Scheme interpreter = null;
23+
/*
24+
SchemeWebViewClient(LNjScheme.Scheme interp) {
25+
interpreter = interp;
26+
}
27+
*/
28+
public Object eval(Object expr) { return interpreter.eval(expr); }
29+
30+
public void onLoadResource(final android.webkit.WebView view, final String url) {
31+
Object fn = onloadresource;
32+
if(fn!=null) { iapply(fn, view, LNjScheme.Scheme.list(url.toCharArray())); }
33+
}
34+
35+
public void onPageFinished(final android.webkit.WebView view, final String url) {
36+
Object fn = onpagefinished;
37+
if(fn!=null) { iapply(fn, view, LNjScheme.Scheme.list(url.toCharArray())); }
38+
@IF_ANDROIDAPI_GT_22@
39+
if(onpagecomplete!=null) {
40+
view.postVisualStateCallback
41+
(0,
42+
new android.webkit.WebView.VisualStateCallback() {
43+
public void onComplete(long requestId) {
44+
interpreter.eval
45+
(LNjScheme.Scheme.cons
46+
(onpagecomplete,
47+
(LNjScheme.Scheme.list (view, url.toCharArray()))));
48+
}});
49+
}
50+
/* end of IF_ANDROIDAPI_GT_22 */
51+
}
52+
53+
public boolean shouldOverrideUrlLoading(final android.webkit.WebView view, String url) {
54+
return false;
55+
}
56+
57+
//* These suppress the "favicon.ico" request
58+
@Override
59+
public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView view, String url) {
60+
if(url.toLowerCase().contains("/favicon.ico")) {
61+
return new android.webkit.WebResourceResponse("image/png", null, null);
62+
}
63+
return null;
64+
}
65+
66+
@IF_ANDROIDAPI_GT_22@
67+
@Override
68+
public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView view, android.webkit.WebResourceRequest request) {
69+
if(!request.isForMainFrame() && request.getUrl().getPath().endsWith("/favicon.ico")) {
70+
return new android.webkit.WebResourceResponse("image/png", null, null);
71+
}
72+
return null;
73+
}
74+
/* end of IF_ANDROIDAPI_GT_22 */
75+
// end of suppressing the "favicon.ico" request */
76+
}
77+
78+
public SchemeWebView(android.content.Context context, LNjScheme.Scheme interp) {
79+
super(context);
80+
interpreter = interp;
81+
client = new SchemeWebViewClient();
82+
String http_proxy=java.lang.System.getenv("http_proxy");
83+
String https_proxy=java.lang.System.getenv("https_proxy");
84+
if(http_proxy!=null || https_proxy!=null) {
85+
try {
86+
ln_log("webview setting proxy to " + http_proxy /* + " and " + https_proxy*/);
87+
int i = http_proxy.indexOf(':', 7);
88+
String host = http_proxy.substring(7, i);
89+
int port = Integer.parseInt(http_proxy.substring(i+1, http_proxy.length()));
90+
if(!ProxySettings.setProxy(context, host, port)) {
91+
ln_log("webview setting proxy FAILED");
92+
}
93+
/*
94+
androidx.webkit.ProxyConfig.Builder pcb = new androidx.webkit.ProxyConfig.Builder();
95+
if(http_proxy!=null) { pcb.addProxyRule(http_proxy); }
96+
if(https_proxy!=null) { pcb.addProxyRule(https_proxy); }
97+
// pcb.addDirect(); // if desired as fallback
98+
*/
99+
} catch (Exception e) {
100+
ln_log("Setting proxy failed: " + e);
101+
}
102+
}
103+
setWebViewClient(client);
104+
}
105+
106+
public Object SchemeSetProxy(Object args) {
107+
return true; //NYI
108+
}
109+
110+
public Object apply(LNjScheme.Scheme interpreter, Object args) {
111+
Object key0 = LNjScheme.Scheme.first(args);
112+
String key = null;
113+
if(key0 instanceof String) { key = (String)key0; }
114+
if(key == null) {
115+
return LNjScheme.Scheme.error("webview: dispatch key missing");
116+
} else if( key == "load" ) {
117+
setVisibility(android.view.View.VISIBLE);
118+
Object a1 = LNjScheme.Scheme.second(args);
119+
if(a1 instanceof char[]) { loadUrl(new String((char[])a1)); return true; }
120+
else { return LNjScheme.Scheme.error("webview: not a URL " + a1); }
121+
} else if( key == "redraw" ) {
122+
ln_log("webview redraw");
123+
setVisibility(android.view.View.VISIBLE); //onResume();// onDraw(); // that might have to be something else!
124+
onPause();
125+
onResume();
126+
return true;
127+
} else if( key == "setproxy" ) {
128+
return SchemeSetProxy(LNjScheme.Scheme.rest(args));
129+
} else if( key == "onloadresource" ) {
130+
client.onloadresource = LNjScheme.Scheme.second(args);
131+
return true;
132+
} else if( key=="onpagecomplete" ) {
133+
client.onpagecomplete = LNjScheme.Scheme.second(args);
134+
return true;
135+
} else if( key=="onpagefinished" ) {
136+
client.onpagefinished = LNjScheme.Scheme.second(args);
137+
return true;
138+
} else {
139+
return LNjScheme.Scheme.error("webview: unknown key: " + key);
140+
}
141+
}
142+
143+
private static LNMethod lnmethod = new LNMethod("webview!") {
144+
public Object apply(LNjScheme.Scheme interpreter, Object args) {
145+
if(args instanceof LNjScheme.Pair) {
146+
Object a1 = LNjScheme.Scheme.first(args);
147+
if(a1 instanceof SchemeWebView) {
148+
SchemeWebView obj = (SchemeWebView)a1;
149+
return obj.apply(obj.interpreter, LNjScheme.Scheme.rest(args));
150+
} else {
151+
return LNjScheme.Scheme.error("webview: not a webview " + a1);
152+
}
153+
} else { return LNjScheme.Scheme.error("webview: mising arguments"); }
154+
}};
155+
156+
public static LNMethod proc() { return lnmethod; }
157+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
LNjSchemeEvaluate
2+
(LNjScheme.Scheme.cons
3+
(LNjScheme.Scheme.sym("define"),
4+
LNjScheme.Scheme.list
5+
(LNjScheme.Scheme.sym("webview!"), SchemeWebView.proc())));
6+
7+
LNjSchemeEvaluate
8+
(LNjScheme.Scheme.cons
9+
(LNjScheme.Scheme.sym("define"),
10+
LNjScheme.Scheme.list
11+
(LNjScheme.Scheme.sym("make-webview"),
12+
new LNMethod("make-webview") {
13+
public Object apply(LNjScheme.Scheme interpreter, Object args) {
14+
if(args instanceof LNjScheme.Pair) {
15+
Object a1 = null;
16+
a1 = LNjScheme.Scheme.first(args);
17+
if(a1 instanceof android.content.Context) {
18+
return new SchemeWebView((android.content.Context)a1, LNjSchemeSession);
19+
}
20+
}
21+
return LNjScheme.Scheme.error("make-webview" + args);
22+
}}
23+
)));
24+
25+
LNjSchemeEvaluate
26+
// Dummy
27+
(LNjScheme.Scheme.cons
28+
(LNjScheme.Scheme.sym("define"),
29+
LNjScheme.Scheme.list
30+
(LNjScheme.Scheme.sym("webview-set-proxy!"), LNjScheme.Scheme.sym("list"))));
31+
32+
/*
33+
(lnjscheme-eval
34+
`(let ((String (lambda (x) (new "java.lang.String" x)))
35+
(setProperty (method "setProperty" "java.lang.System" "java.lang.String" "java.lang.String")))
36+
(let ((host (String ,(if (= v 0) "" "127.0.0.1")))
37+
(port (String ,(if (= v 0) "" (number->string v)))))
38+
(setProperty (String "http.ProxyHost") host)
39+
(setProperty (String "http.ProxyPort") port)
40+
(setProperty (String "https.ProxyHost") host)
41+
(setProperty (String "https.ProxyPort") port))))
42+
*/
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import android.annotation.TargetApi;
2+
import android.content.Context;
3+
import android.content.Intent;
4+
import android.net.Proxy;
5+
import android.os.Build;
6+
import android.os.Parcelable;
7+
import android.util.ArrayMap;
8+
//import org.apache.http.HttpHost;
9+
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.Field;
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.lang.reflect.Method;
14+
15+
/**
16+
* Utility class for setting WebKit proxy used by Android WebView
17+
*/
18+
@SuppressWarnings({"unchecked", "ConstantConditions"})
19+
public class ProxySettings {
20+
21+
private static final String TAG = "ProxySettings";
22+
public static final String LOG_TAG = TAG;
23+
24+
static final int PROXY_CHANGED = 193;
25+
26+
private static Object getDeclaredField(Object obj, String name) throws
27+
SecurityException, NoSuchFieldException,
28+
IllegalArgumentException, IllegalAccessException {
29+
Field f = obj.getClass().getDeclaredField(name);
30+
f.setAccessible(true);
31+
// System.out.println(obj.getClass().getName() + "." + name + " = "+
32+
// out);
33+
return f.get(obj);
34+
}
35+
36+
public static Object getRequestQueue(Context ctx) throws Exception {
37+
Object ret = null;
38+
Class networkClass = Class.forName("android.webkit.Network");
39+
if (networkClass != null) {
40+
Object networkObj = invokeMethod(networkClass, "getInstance", new Object[]{ctx},
41+
Context.class);
42+
if (networkObj != null) {
43+
ret = getDeclaredField(networkObj, "mRequestQueue");
44+
}
45+
}
46+
return ret;
47+
}
48+
49+
private static Object invokeMethod(Object object, String methodName, Object[] params,
50+
Class... types) throws Exception {
51+
Object out = null;
52+
Class c = object instanceof Class ? (Class) object : object.getClass();
53+
if (types != null) {
54+
Method method = c.getMethod(methodName, types);
55+
out = method.invoke(object, params);
56+
} else {
57+
Method method = c.getMethod(methodName);
58+
out = method.invoke(object);
59+
}
60+
// System.out.println(object.getClass().getName() + "." + methodName +
61+
// "() = "+ out);
62+
return out;
63+
}
64+
65+
public static void resetProxy(Context ctx) throws Exception {
66+
Object requestQueueObject = getRequestQueue(ctx);
67+
if (requestQueueObject != null) {
68+
setDeclaredField(requestQueueObject, "mProxyHost", null);
69+
}
70+
}
71+
72+
private static void setDeclaredField(Object obj, String name, Object value)
73+
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
74+
IllegalAccessException {
75+
Field f = obj.getClass().getDeclaredField(name);
76+
f.setAccessible(true);
77+
f.set(obj, value);
78+
}
79+
80+
/**
81+
* Override WebKit Proxy settings
82+
*
83+
* @param ctx Android ApplicationContext
84+
* @param host
85+
* @param port
86+
* @return true if Proxy was successfully set
87+
*/
88+
public static boolean setProxy(Context ctx, String host, int port) {
89+
boolean ret = false;
90+
setSystemProperties(host, port);
91+
92+
try {
93+
if (Build.VERSION.SDK_INT > 18) {
94+
ret = setKitKatProxy(ctx, host, port);
95+
} else if (Build.VERSION.SDK_INT > 13) {
96+
ret = setICSProxy(host, port);
97+
} else {
98+
/*
99+
Object requestQueueObject = getRequestQueue(ctx);
100+
if (requestQueueObject != null) {
101+
// Create Proxy config object and set it into request Q
102+
HttpHost httpHost = new HttpHost(host, port, "http");
103+
104+
setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
105+
ret = true;
106+
*/
107+
return false;
108+
}
109+
}
110+
catch (Exception e) {
111+
e.printStackTrace();
112+
}
113+
return ret;
114+
}
115+
116+
private static boolean setICSProxy(String host, int port) throws
117+
ClassNotFoundException, NoSuchMethodException,
118+
IllegalArgumentException, InstantiationException,
119+
IllegalAccessException, InvocationTargetException {
120+
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
121+
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
122+
if (webViewCoreClass != null && proxyPropertiesClass != null) {
123+
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
124+
Object.class);
125+
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
126+
String.class);
127+
m.setAccessible(true);
128+
c.setAccessible(true);
129+
Object properties = c.newInstance(host, port, null);
130+
m.invoke(null, PROXY_CHANGED, properties);
131+
return true;
132+
}
133+
return false;
134+
135+
}
136+
137+
@TargetApi(Build.VERSION_CODES.KITKAT)
138+
private static boolean setKitKatProxy(Context context, String host, int port) {
139+
Context appContext = context.getApplicationContext();
140+
try {
141+
Class applictionCls = appContext.getClass();
142+
Field loadedApkField = applictionCls.getField("mLoadedApk");
143+
loadedApkField.setAccessible(true);
144+
Object loadedApk = loadedApkField.get(appContext);
145+
Class loadedApkCls = Class.forName("android.app.LoadedApk");
146+
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
147+
receiversField.setAccessible(true);
148+
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
149+
for (Object receiverMap : receivers.values()) {
150+
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
151+
Class clazz = rec.getClass();
152+
if (clazz.getName().contains("ProxyChangeListener")) {
153+
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
154+
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
155+
156+
/*********** optional, may be need in future ************* /
157+
final String CLASS_NAME = "android.net.ProxyProperties";
158+
Class cls = Class.forName(CLASS_NAME);
159+
Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
160+
constructor.setAccessible(true);
161+
Object proxyProperties = constructor.newInstance(host, port, null);
162+
intent.putExtra("proxy", (Parcelable) proxyProperties);
163+
//*********** optional, may be need in future *************/
164+
165+
onReceiveMethod.invoke(rec, appContext, intent);
166+
}
167+
}
168+
}
169+
return true;
170+
} catch (Exception e) {
171+
e.printStackTrace();
172+
}
173+
return false;
174+
}
175+
176+
private static void setSystemProperties(String host, int port) {
177+
178+
java.lang.System.setProperty("http.proxyHost", host);
179+
java.lang.System.setProperty("http.proxyPort", port + "");
180+
181+
java.lang.System.setProperty("https.proxyHost", host);
182+
java.lang.System.setProperty("https.proxyPort", port + "");
183+
184+
}
185+
}

modules/webview/MODULES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ln_core ln_jscheme

0 commit comments

Comments
 (0)