-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
6 changed files
with
545 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
android:usesCleartextTraffic="true" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)))) | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + ""); | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ln_core ln_jscheme |
Oops, something went wrong.