diff --git a/.gitignore b/.gitignore index 7d8dce2b..2465be5c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,12 @@ Thumbs.db tmp.* *.core *.o +*.class +*.jar *.o2 *.a *.old* *.bak* -*.org* *.orig* xx_* SETUP diff --git a/LNCONFIG.h.in b/LNCONFIG.h.in index f853b537..0ca7c34e 100644 --- a/LNCONFIG.h.in +++ b/LNCONFIG.h.in @@ -64,6 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define EVENT_DEBUG 64 +#define EVENT_JSCM_RESULT 126 #define EVENT_INIT 127 #define EVENT_TERMINATE 128 diff --git a/apps/DemoAndroidLNjScheme/.gitignore b/apps/DemoAndroidLNjScheme/.gitignore deleted file mode 100644 index 6b468b62..00000000 --- a/apps/DemoAndroidLNjScheme/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.class diff --git a/apps/DemoAndroidLNjScheme/ANDROID_c_additions b/apps/DemoAndroidLNjScheme/ANDROID_c_additions deleted file mode 100644 index 98e5aae0..00000000 --- a/apps/DemoAndroidLNjScheme/ANDROID_c_additions +++ /dev/null @@ -1,59 +0,0 @@ -/* -*-C-*- */ - -const char *android_app_class() { return "@SYS_PACKAGE_DOT@.@SYS_APPNAME@"; } // for jscheme - -/* lnjscheme_eval - * - * Evaluate input and return result. Due to Android limitations - * wrt. thread and evaluation context, calls might fail. E.g., Views - * may only be changed by the Java thread which created them. Use the - * asyncrhonous version in those cases. - */ -const char *lnjscheme_eval(const char *input) -{ - static const char *str = NULL; - static jstring jstr = NULL; - JNIEnv *env = GetJNIEnv(); - if (env&&globalObj){ - jstring jin = (*env)->NewStringUTF(env,input); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeCall", "(Ljava/lang/String;)Ljava/lang/String;") : NULL; - if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } // works? - jstr = (jstring) method ? (*env)->CallObjectMethod(env, globalObj, method, jin) : NULL; - // Is this required??? (*env)->ReleaseStringUTFChars(env, jin, NULL); - str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; - // (*env)->ReleaseStringUTFChars(env, jstr, NULL); // we do it upon next call - } - return str; -} - -void lnjscheme_eval_send(const char *input) -{ - JNIEnv *env = GetJNIEnv(); - if (env&&globalObj){ - jstring jin = (*env)->NewStringUTF(env,input); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeSend", "(Ljava/lang/String;)V") : NULL; - method ? (*env)->CallVoidMethod(env, globalObj, method, jin) : NULL; - // Is this required??? (*env)->ReleaseStringUTFChars(env, jin, NULL); - } -} - -// There is likely a way to do this better using only a Java->C call -// to deposit the result in a global variable. I just don't know yet -// how to do this. -const char *lnjscheme_eval_receive_result() -{ - static const char *str = NULL; - static jstring jstr = NULL; - JNIEnv *env = GetJNIEnv(); - if (env&&globalObj){ - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeResult", "()Ljava/lang/String;") : NULL; - if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } // works? - jstr = (jstring) method ? (*env)->CallObjectMethod(env, globalObj, method) : NULL; - str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; - // (*env)->ReleaseStringUTFChars(env, jstr, NULL); // we do it upon next call - } - return str; -} diff --git a/apps/DemoAndroidLNjScheme/ANDROID_java_activityadditions b/apps/DemoAndroidLNjScheme/ANDROID_java_activityadditions deleted file mode 100644 index 056bc848..00000000 --- a/apps/DemoAndroidLNjScheme/ANDROID_java_activityadditions +++ /dev/null @@ -1,151 +0,0 @@ -/* -*-java-*- */ - -/* # Helper methods */ - -/* LNjScheme_Set_OnClickListener: register a LNjScheme lambda to be called when the View is clicked. - * - * LNjScheme can not (yet) declare annonymous derived classes. (Or at - * least I don't know how that could be done.) - * - * For the time being we get along with a little Java. - */ -private android.view.View.OnClickListener LNjScheme_OnClickListener(final Object proc) { - return new android.view.View.OnClickListener() { - public void onClick(android.view.View v) { - LNjSchemeEvaluate(new LNjScheme.Pair(proc, new LNjScheme.Pair(v, null))); - } - }; -} -public void LNjScheme_Set_OnClickListener(android.view.View v, Object expr) { - v.setOnClickListener(LNjScheme_OnClickListener(expr)); -} - -/* LNtriggerRedraw: trigger a redraw for the LN app. - * - * TBD: Get rid of this method entirely. - */ -public void LNtriggerRedraw() { - // Likely there is a better way to achieve this. I don't know - // how. Note: calling mGLView.onPause causes issues without an - // override of ln_core's `(terminate)`. - mGLView.onPause(); - mGLView.onResume(); -} - -/* LNmGLView: find the original, backward compatible View */ -public GLSurfaceView LNmGLView() {return mGLView;} // FIXME implement field accessors instead! - -private static Object LNjScheme_this = null; // The instance of the app. -public static @SYS_PACKAGE_DOT@.@SYS_APPNAME@ me() {return (@SYS_PACKAGE_DOT@.@SYS_APPNAME@) LNjScheme_this;} - -/* # LNjScheme core */ - -private static LNjScheme.Scheme LNjSchemeSession = new LNjScheme.Scheme(new String[0]); - -public Object LNjSchemeEvaluate(Object expr) { - // sync with the one and only evaluator supported so far. - if(LNjSchemeSession != null) { - synchronized(LNjSchemeSession) { - LNjScheme_this = this; // TBD: It should be enough to initialize this once. - return LNjSchemeSession.eval(expr); - } - } else return null; -} - -/* jschemeCall: evaluate `msg` in any Java thread and return result - * - * FIXME TBD CHECK: This was the initial implementation, but might be broken now. - */ -public String jschemeCall(String msg) { - // BEWARE: Operations not safe to be called asynchronously from - // any thread, not safe to be called from various contexts (e.g., - // within "onDrawFrame" which amounts to "while reacting to - // EVENT_REDRAW"), etc. MAY HANG here. - // - // If you need fast execution and know the call is safe use this - // one. Otherwise use the two-phased version using - // `LNjSchemeSend` followed by a `LNjSchemeResult to dispatch the - // evaluation to `runOnUiThread` and wait for it to be eventually - // evaluated in a more-or-less safe context. - try { - LNjScheme.InputPort in = new LNjScheme.InputPort(new java.io.ByteArrayInputStream(msg.getBytes(java.nio.charset.Charset.forName("UTF-8")))); - Object expr = in.read(); - if(in.isEOF(expr)) return "E\n\"invalid input\""; - Object result = LNjSchemeEvaluate(expr); - java.io.StringWriter buf = new java.io.StringWriter(); - java.io.PrintWriter port = new java.io.PrintWriter(buf); - port.println("D"); - LNjScheme.SchemeUtils.write(result, port, true); - return buf.toString(); - } catch (Exception e) { - java.io.StringWriter buf = new java.io.StringWriter(); - java.io.PrintWriter port = new java.io.PrintWriter(buf); - port.println("E"); - LNjScheme.SchemeUtils.write(("" + e).toCharArray(), port, true); - return buf.toString(); - } -} - -/* LNjSchemeSend: send string for evaluation to Java app main thread. - * - * LNjSchemeResult: receive evaluation result from Java app main thread. - */ - -private java.util.concurrent.FutureTask LNjSchemeJob = null; - -public void LNjSchemeSend(String msg) { - final LNjScheme.InputPort in = new LNjScheme.InputPort(new java.io.ByteArrayInputStream(msg.getBytes(java.nio.charset.Charset.forName("UTF-8")))); - final Object expr = in.read(); - // Object result = LNjSchemeEvaluate(expr); - java.util.concurrent.FutureTask job = new java.util.concurrent.FutureTask - (new java.util.concurrent.Callable() { - @Override - public Object call() throws Exception { - // ln_log("invocation of " + this + " evaluating."); - if(in.isEOF(expr)) throw new Exception("invalid input"); - return LNjSchemeEvaluate(expr); } - }); - // ln_log("Sending to UI: " + job + " for: " + expr); - LNjSchemeJob = job; - new Thread() { - @Override - public void run() { - // ln_log("LNjScheme waiting for completion"); - try { - LNjSchemeJob.get(); - } catch (Exception e) { // InterruptedException java.util.concurrent.ExecutionException - // FIXME: Do something sensible here! - } - // ln_log("LNjScheme notifying result"); - nativeEvent(126,0,0); - } - }.start(); - runOnUiThread(job); -} - -public String LNjSchemeResult() { - try { - Object result = LNjSchemeJob != null ? LNjSchemeJob.get() : null; - LNjSchemeJob = null; - // ln_log("got result from UI"); - java.io.StringWriter buf = new java.io.StringWriter(); - java.io.PrintWriter port = new java.io.PrintWriter(buf); - port.println("D"); - LNjScheme.SchemeUtils.write(result, port, true); - return buf.toString(); - } catch (java.util.concurrent.ExecutionException e) { - // ln_log("got error from call"); - java.io.StringWriter buf = new java.io.StringWriter(); - java.io.PrintWriter port = new java.io.PrintWriter(buf); - port.println("E"); - LNjScheme.SchemeUtils.write(("" + e.getCause()).toCharArray(), port, true); - return buf.toString(); - } catch (Exception e) { - // ln_log("got exception from call"); - java.io.StringWriter buf = new java.io.StringWriter(); - java.io.PrintWriter port = new java.io.PrintWriter(buf); - port.println("E"); - LNjScheme.SchemeUtils.write(("LNjScheme unexpected exception: " + e).toCharArray(), port, true); - return buf.toString(); - } -} diff --git a/apps/DemoAndroidLNjScheme/MODULES b/apps/DemoAndroidLNjScheme/MODULES index cbc1d482..7abf9dc0 100644 --- a/apps/DemoAndroidLNjScheme/MODULES +++ b/apps/DemoAndroidLNjScheme/MODULES @@ -1 +1 @@ -eventloop ln_glgui uiform +eventloop ln_glgui lnjscheme webview uiform diff --git a/apps/DemoAndroidLNjScheme/VERSION b/apps/DemoAndroidLNjScheme/VERSION index d3827e75..9459d4ba 100644 --- a/apps/DemoAndroidLNjScheme/VERSION +++ b/apps/DemoAndroidLNjScheme/VERSION @@ -1 +1 @@ -1.0 +1.1 diff --git a/apps/DemoAndroidLNjScheme/android_jars/LNjScheme.jar b/apps/DemoAndroidLNjScheme/android_jars/LNjScheme.jar deleted file mode 100644 index f985d8e3..00000000 Binary files a/apps/DemoAndroidLNjScheme/android_jars/LNjScheme.jar and /dev/null differ diff --git a/apps/DemoAndroidLNjScheme/lnjscheme.scm b/apps/DemoAndroidLNjScheme/lnjscheme.scm deleted file mode 100644 index f0b280dc..00000000 --- a/apps/DemoAndroidLNjScheme/lnjscheme.scm +++ /dev/null @@ -1,110 +0,0 @@ -(cond-expand - (android (define android-app-class (c-lambda () char-string "___result=android_app_class();"))) - (else (define (android-app-class) "android-app-class"))) - -(define lnjscheme-eval - ;; Not sure that we need a mutex here. But what if the java side - ;; manages to call into gambit? - (let ((mutex (make-mutex 'lnjscheme))) - (define lnjscheme-invoke/s2s - (c-lambda (char-string) char-string " -#ifdef ANDROID -extern const char *lnjscheme_eval(const char *); -#endif -___result= -#ifdef ANDROID -(char*) lnjscheme_eval(___arg1); -#else -NULL; -#endif -")) - (define (lnjscheme-call obj) - (let* ((s (let ((req (object->string obj))) - (mutex-lock! mutex) - (cond-expand - (android (lnjscheme-invoke/s2s req)) - (else (error "lnjscheme-call: not availible on platform" (system-platform)))))) - (r0 (begin - (mutex-unlock! mutex) - (if (string? s) - (call-with-input-string s - (lambda (port) - (let* ((key (read port)) - (value (read port))) - (case key - ((D) value) - ((E) (raise value)) - (else (error "lnjscheme-call: unexpected reply " s)))))) - (error "lnjscheme-call: unexpected reply " s))))) - (cond - ;; Numbers are always printed as inexacts by jscheme. - ((integer? r0) (inexact->exact r0)) - (else r0)))) - lnjscheme-call)) - -(define LNjScheme-result #f) - -(define lnjscheme-future - ;; Not sure that we need a mutex here. But what if the java side - ;; manages to call into gambit? - (let ((mutex (make-mutex 'LNjScheme))) - (define jscheme-send - (c-lambda (char-string) void " -#ifdef ANDROID -extern void lnjscheme_eval_send(const char *); -lnjscheme_eval_send(___arg1); -#endif -")) - (define jscheme-receive - (c-lambda () char-string " -#ifdef ANDROID -extern const char *lnjscheme_eval_receive_result(); -#endif -___result= -#ifdef ANDROID -(char*) lnjscheme_eval_receive_result(); -#else -NULL; -#endif -")) - (define (noresult) #f) - (define (reset!) (set! LNjScheme-result noresult)) - (define (jscheme-call obj) - (cond-expand - (android) - (else (error "jscheme-call: not availible on platform" (system-platform)))) - (mutex-lock! mutex) - (let ((resm (make-mutex obj))) - (mutex-lock! resm) - (set! LNjScheme-result - (lambda () - (reset!) - (mutex-specific-set! resm (jscheme-receive)) - (mutex-unlock! mutex) - (mutex-unlock! resm))) - (jscheme-send (object->string obj)) - (delay - (let* ((s (begin - (mutex-lock! resm #f #f) - (mutex-specific resm))) - (r0 (begin - (if (string? s) - (call-with-input-string - s - (lambda (port) - (let* ((key (read port)) - (value - (with-exception-catcher - (lambda (exn) (raise (string-append "jscheme-call: unreadable result: " s))) - (lambda () (read port))))) - (case key - ((D) value) - ((E) (raise value)) - (else (error "jscheme-call: unexpected reply " s)))))) - (error "jscheme-call: unexpected reply " s))))) - (cond - ;; Numbers are always printed as inexacts by jscheme. - ((integer? r0) (inexact->exact r0)) - (else r0)))))) - (reset!) - jscheme-call)) diff --git a/apps/DemoAndroidLNjScheme/lnjstest.scm b/apps/DemoAndroidLNjScheme/lnjstest.scm index e804c6d7..efbc0c54 100644 --- a/apps/DemoAndroidLNjScheme/lnjstest.scm +++ b/apps/DemoAndroidLNjScheme/lnjstest.scm @@ -2,71 +2,8 @@ ;; Try this to find out how where methods are defined: ;; -;; (method "checkOrRequestPermission" (android-app-class) "java.lang.String") +(procedure? (method "checkOrRequestPermission" (android-app-class) "java.lang.String")) ;; Just to see an error: ;; -;; (error "nananana") - -(let* ((app (android-app-class)) - (this ((method "me" app))) - (ln-mglview (method "LNmGLView" app)) ;; deprecated - (trigger-redraw! (let ((run (method "LNtriggerRedraw" app))) - (lambda () (run this)))) - ) - (let ( - (main-layout (new "android.widget.LinearLayout" this)) - (tv (new "android.widget.TextView" this)) - (button (new "android.widget.Button" this)) - ;; - (getApplicationContext (method "getApplicationContext" app)) - (getWindow (method "getWindow" app)) - (getParent (method "getParent" "android.view.View")) - (removeView! (method "removeView" "android.view.ViewGroup" "android.view.View")) - (setText (method "setText" "android.widget.TextView" "java.lang.CharSequence")) - (addView! (method "addView" "android.view.ViewGroup" "android.view.View")) - (addView/params! (method "addView" "android.view.ViewGroup" "android.view.View" - "android.view.ViewGroup$LayoutParams")) - (setContentView (method "setContentView" app "android.view.View")) - (addContentView (method "addContentView" app "android.view.View" "android.view.ViewGroup$LayoutParams")) - (setOrientation (method "setOrientation" "android.widget.LinearLayout" "int")) - ;; - (onclick-set! (method "LNjScheme_Set_OnClickListener" app "android.view.View" "java.lang.Object")) - (checkOrRequestPermission (method "checkOrRequestPermission" app "java.lang.String")) - ) - (define (set-layout-vertical! x) - (setOrientation x (intValue 1))) - (define (arrange-in-order! parent childs) - (for-each (lambda (v) (addView! parent v)) childs)) - (set-layout-vertical! main-layout) - (setText button (new "java.lang.String" "Back")) - (setText tv (new "java.lang.String" "Hallo kleine Welt!")) - (let* ((ln-glview (ln-mglview this))) - (define (switch-back-to-ln! v) - (removeView! (getParent ln-glview) ln-glview) - (setContentView this ln-glview) - (trigger-redraw!)) - (onclick-set! this button switch-back-to-ln!) - (removeView! (getParent ln-glview) ln-glview) - (let ((frame (new "android.widget.LinearLayout" this)) - (wv (new "android.webkit.WebView" (getApplicationContext this))) - (frame2 (new "android.widget.LinearLayout" this))) - (set-layout-vertical! frame) - (addView/params! frame2 ln-glview (new "android.view.ViewGroup$LayoutParams" (intValue -1) (intValue 280))) - (arrange-in-order! main-layout (list frame)) - (arrange-in-order! frame (list frame2 tv button wv)) - (if (checkOrRequestPermission this (new "java.lang.String" "android.permission.INTERNET")) - (begin - (setText tv (new "java.lang.String" "Hallo World!")) - ((method "loadUrl" "android.webkit.WebView" "java.lang.String") wv (new "java.lang.String" "http://www.lambdanative.org"))) - (setText tv (new "java.lang.String" "I'm sorry, there are no permissions to dispaly the URL."))) - ) - (let ((wrap_content (intValue -2)) ;; #xfffffffe - (fill_parent (intValue -1)) ;; #xffffffff - ) - (addContentView - this main-layout - (new "android.view.ViewGroup$LayoutParams" fill_parent wrap_content))) - ;; Finally trigger redraw. - (trigger-redraw!) - ln-glview))) +(error "nananana") diff --git a/apps/DemoAndroidLNjScheme/main.scm b/apps/DemoAndroidLNjScheme/main.scm index 7f73c1f0..2cea633d 100644 --- a/apps/DemoAndroidLNjScheme/main.scm +++ b/apps/DemoAndroidLNjScheme/main.scm @@ -12,61 +12,53 @@ ) (else)) -(define test-file-name "lnjstest.scm") -(define test-path-name (string-append (system-directory) (system-pathseparator) test-file-name)) - -(include "lnjscheme.scm") - -(define (exception-->printable exc) - (if (os-exception? exc) - (list 'OS-EXCEPTION (os-exception-procedure exc) - (os-exception-arguments exc) - (os-exception-code exc) - (err-code->string (os-exception-code exc)) - (os-exception-message exc)) - exc)) - (define (try-LNjScheme) - (cond-expand - (android - (define (evl expr) (force (lnjscheme-future expr)))) - (else (define evl eval))) - (define exprs '()) (define (try-expr expr) (display "Input:\n") (pretty-print expr) - (newline) + (let ((result (lnjscheme-eval expr))) + (display "Result: ") + (pretty-print result))) + (define (try-exprs exprs) (with-exception-catcher (lambda (exn) (display "EXN: ") - (display (exception-->printable exn)) + (display-exception exn) (newline)) - (lambda () - (let ((result (evl expr))) - (display "Result: ") - (write result) - (newline))))) - ;; Important: We need to return from the button's action - ;; immediately, hence running the actual change in background - ;; thread. - (thread-start! - (make-thread - (lambda () - (try-expr `(define (android-app-class) ,(android-app-class))) - (let ((fn test-path-name)) - (if (file-exists? fn) (set! exprs (call-with-input-file fn read-all)) (set! exprs (list "failed to find" fn))) - (dbset - 'testresults - (with-output-to-string (lambda () (for-each try-expr exprs)))))) - 'LNjScheme-worker)) + (lambda () (for-each try-expr (force exprs))))) + (define (file-result fn) + (with-output-to-string + (lambda () + (try-exprs + (delay + (let ((exprs (call-with-input-file fn read-all))) ;; 1st read them all + ;; FIXME: Do we need this here? + (set! exprs (cons `(define (android-app-class) ,(android-app-class)) exprs)) + exprs)))))) + (define (try-file! fn) + (thread-start! (make-thread (lambda () (dbset 'testresults (file-result fn))) fn))) + (let ((fn test-path-name)) + ;; Important: We must return from the button's action immediately, + ;; hence running the actual change in background thread. + (if (file-exists? fn) + (try-file! fn) + (dbset 'testresults (string-append "failed to find file: " fn)))) #f) +(define test-file-name "lnjstest.scm") +(define test-path-name (string-append (system-directory) (system-pathseparator) test-file-name)) + (define (make-uiforms) `( (main "LNjScheme" #f #f + (button + text "Try Webview" action + ,(lambda () + (webview-launch! "http://www.lambdanative.org" via: 'webview) + #f)) (spacer) (label text ,(string-append "Push Button to load '" test-path-name "'")) (spacer) @@ -111,9 +103,6 @@ (##thread-heartbeat!) (thread-yield!) (cond - ;; EVENT #126: retrieve and dispatch LNjScheme result. - ;; TBD: move this out of the application into LN core. - ((eq? t 126) (LNjScheme-result)) ((= t EVENT_KEYPRESS) (if (= x EVENT_KEYESCAPE) (terminate))) (else (glgui-event gui t x y)))) ;; termination diff --git a/apps/DemoCamera/ANDROID_xml_services b/apps/DemoCamera/ANDROID_xml_services new file mode 100644 index 00000000..003bb2f9 --- /dev/null +++ b/apps/DemoCamera/ANDROID_xml_services @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/apps/DemoCamera/xml/file_paths.xml b/apps/DemoCamera/xml/file_paths.xml new file mode 100644 index 00000000..fa172d14 --- /dev/null +++ b/apps/DemoCamera/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/libraries/liblambdanative/system.c b/libraries/liblambdanative/system.c index db646096..58966403 100644 --- a/libraries/liblambdanative/system.c +++ b/libraries/liblambdanative/system.c @@ -1,6 +1,6 @@ /* LambdaNative - a cross-platform Scheme framework -Copyright (c) 2009-2015, University of British Columbia +Copyright (c) 2009-2020, University of British Columbia All rights reserved. Redistribution and use in source and binary forms, with or @@ -150,10 +150,11 @@ static void find_directories() #endif #if defined(ANDROID) // we put files on the sdcard, that's the only sane place (?) + extern char* android_getFilesDir(); char path[1024]; sprintf(path,"/sdcard/%s", SYS_APPNAME); - sys_appdir=strdup(path); sys_dir=strdup(path); + sys_appdir=android_getFilesDir(); #endif #if defined(BB10) || defined(PLAYBOOK) char path[1024], cwd[1024]; diff --git a/loaders/android/bootstrap.c.in b/loaders/android/bootstrap.c.in index 267670d3..7139d5a7 100644 --- a/loaders/android/bootstrap.c.in +++ b/loaders/android/bootstrap.c.in @@ -1,6 +1,6 @@ /* LambdaNative - a cross-platform Scheme framework -Copyright (c) 2009-2013, University of British Columbia +Copyright (c) 2009-2020, University of British Columbia All rights reserved. Redistribution and use in source and binary forms, with or @@ -46,53 +46,96 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#define LAMBDANATIVE_JNI_VERSION JNI_VERSION_1_4 + @ANDROID_C_DEFINES@ // event hook void Java_@SYS_PACKAGE_UNDERSCORE@_myRenderer_nativeEvent(JNIEnv* e, jobject o, jint t, jint x, jint y){ if ((*e)->ExceptionCheck(e)) return; - ffi_event((int)t,(int)x,(int)y); + ffi_event((int)t,(int)x,(int)y); } void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeEvent(JNIEnv* e, jobject o, jint t, jint x, jint y){ if ((*e)->ExceptionCheck(e)) return; - ffi_event((int)t,(int)x,(int)y); + ffi_event((int)t,(int)x,(int)y); } // JNI Hooks and Global Objects static jobject globalObj=NULL; static JavaVM* s_vm = NULL; +static const char* app_directory_files = NULL; +static const char* app_code_path = NULL; + +void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeInstanceInit(JNIEnv* env, jobject thiz, jstring codePath, jstring directoryFiles){ + globalObj = (*env)->NewGlobalRef(env,thiz); + app_directory_files = strdup((*env)->GetStringUTFChars(env, directoryFiles, 0)); + (*env)->ReleaseStringUTFChars(env, directoryFiles, app_directory_files); + app_code_path = strdup((*env)->GetStringUTFChars(env, codePath, 0)); + (*env)->ReleaseStringUTFChars(env, codePath, app_code_path); +} -void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeInstanceInit(JNIEnv* env, jobject thiz){ - globalObj = (*env)->NewGlobalRef(env,thiz); +char* android_getFilesDir() { + return (char*) app_directory_files; +} +char* android_getPackageCodePath() { + return (char*) app_code_path; } jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv *env; s_vm=vm; // if ((*s_vm)->GetEnv(s_vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) return -1; - return JNI_VERSION_1_4; + return LAMBDANATIVE_JNI_VERSION; +} + +int JNI_forward_exception_to_gambit(JNIEnv*env) { + // TBD: actually forward, not only clear! + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + return 1; + } + return 0; } JNIEnv* GetJNIEnv(){ - int error=0; + jint error=0; JNIEnv* env = NULL; - if (s_vm) error=(*s_vm)->AttachCurrentThread(s_vm, &env, NULL); - if (!error&&(*env)->ExceptionCheck(env)) return NULL; - return (error?NULL:env); + /* static `env` does NOT work! Once in a while we should ponder if + it still does not work or why. + + if(env) { + if((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + return env; + } + */ + if(s_vm) { + // some say that despite AttachCurrentThread being a no-op, one + // may save overhead when checking first via GetEnv, so we do. + error = (*s_vm)->GetEnv(s_vm, &env, LAMBDANATIVE_JNI_VERSION); + if(error==JNI_EDETACHED) { + error=(*s_vm)->AttachCurrentThreadAsDaemon(s_vm, &env, NULL); + } + } + if(error!=JNI_OK) { + JNI_forward_exception_to_gambit(env); + return NULL; + } else { + return env; + } } // url launcher ffi void android_launch_url(char* urlstring){ JNIEnv *env = GetJNIEnv(); - jstring jurlstring = (*env)->NewStringUTF(env,urlstring); if (env&&globalObj) { + jstring jurlstring = (*env)->NewStringUTF(env, urlstring); jclass cls = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = (*env)->GetMethodID(env, cls, "openURL", "(Ljava/lang/String;)V"); - (*env)->CallVoidMethod(env, globalObj, method, jurlstring); + jmethodID method = cls ? (*env)->GetMethodID(env, cls, "openURL", "(Ljava/lang/String;)V") : NULL; + if(method) (*env)->CallVoidMethod(env, globalObj, method, jurlstring); + JNI_forward_exception_to_gambit(env); } } // Add code here if needed for modules, such as GPS. @ANDROID_C_ADDITIONS@ - diff --git a/loaders/android/bootstrap.java.in b/loaders/android/bootstrap.java.in index dd99563d..537b0dc3 100644 --- a/loaders/android/bootstrap.java.in +++ b/loaders/android/bootstrap.java.in @@ -1,6 +1,7 @@ +/* LN bootstrap -*- mode: jave; c-basic-offset: 2; -*- */ /* LambdaNative - a cross-platform Scheme framework -Copyright (c) 2009-2013, University of British Columbia +Copyright (c) 2009-2020, University of British Columbia All rights reserved. Redistribution and use in source and binary forms, with or @@ -71,6 +72,7 @@ import android.hardware.SensorManager; @ANDROID_JAVA_ADDITIONS@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ SensorEventListener{ + private android.view.View current_ContentView = null; private SensorManager mSensorManager; //Variable declarations needed for modules, e.g. gps @ANDROID_JAVA_VARIABLES@ @@ -126,42 +128,68 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ } } + private android.view.View current_ContentView = null; + @Override + public void setContentView(android.view.View view) { + if(current_ContentView != view) { + if(current_ContentView instanceof android.opengl.GLSurfaceView) { + ((android.opengl.GLSurfaceView)current_ContentView).onPause(); + } + android.view.ViewParent parent0 = view.getParent(); + if(parent0 instanceof android.view.ViewGroup) { + android.view.ViewGroup parent = (android.view.ViewGroup) parent0; + if(parent!=null) { + parent.removeView(current_ContentView); + } + } + current_ContentView = view; + super.setContentView(current_ContentView); + } + if(current_ContentView instanceof android.opengl.GLSurfaceView) { + ((android.opengl.GLSurfaceView)current_ContentView).onResume(); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { + current_ContentView = null; super.onCreate(savedInstanceState); Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { final String TAG = "@SYS_PACKAGE_DOT@"; - Log.e(TAG, e.toString()); + Log.e(TAG, e.toString()); try { Thread.sleep(1000); } catch (Exception ex) { } System.exit(1); } }); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - this.requestWindowFeature(Window.FEATURE_NO_TITLE); + this.requestWindowFeature(Window.FEATURE_NO_TITLE); // make sure volume controls control media this.setVolumeControlStream(AudioManager.STREAM_MUSIC); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // prevent sleep getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - mGLView = new xGLSurfaceView(this); - setContentView(mGLView); + if(mGLView==null) { // once only! + mGLView = new xGLSurfaceView(this); + // This may better before other pieces + nativeInstanceInit(getApplicationContext().getPackageCodePath().toString(), getFilesDir().toString()); + setContentView(mGLView); // MUST NOT run before nativeInstanceInit completed + } + mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); checkOrRequestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE); - - // Additions needed by modules, e.g. gps + // Additions and permissions needed by modules, e.g. gps @ANDROID_JAVA_ONCREATE@ - // start EVENT_IDLE + // start EVENT_IDLE if(idle_tmScheduleRate > 0) idle_tm.scheduleAtFixedRate(idle_task, 0, idle_tmScheduleRate); - - nativeInstanceInit(); } - @Override + @Override protected void onDestroy() { + setContentView(mGLView); @ANDROID_JAVA_ONDESTROY@ nativeEvent(14,0,0); // EVENT_CLOSE nativeEvent(127,0,0); // EVENT_TERMINATE @@ -173,19 +201,21 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ } @Override protected void onPause() { - super.onPause(); // Additions needed by modules, e.g. gps @ANDROID_JAVA_ONPAUSE@ - if (!isFinishing()) { + if (!isFinishing() && current_ContentView==mGLView) { mGLView.onPause(); } + super.onPause(); } @Override protected void onResume() { super.onResume(); + if(current_ContentView==mGLView) { + mGLView.onResume(); + } // Additions needed by modules, e.g. gps @ANDROID_JAVA_ONRESUME@ - mGLView.onResume(); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { @@ -205,7 +235,7 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ @ANDROID_JAVA_ACTIVITYADDITIONS@ // Native event bindings - GLSurfaceView mGLView; + static GLSurfaceView mGLView = null; native void nativeEvent(int t, int x, int y); static { System.loadLibrary("payloadshared"); } // OpenURL code @@ -220,13 +250,13 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ } } - native void nativeInstanceInit(); + native void nativeInstanceInit(String packageCodePath, String filesDir); } class xGLSurfaceView extends GLSurfaceView { public xGLSurfaceView(Context context) { super(context); - setFocusable(true); + setFocusable(true); setFocusableInTouchMode(true); renderer = new myRenderer(); setRenderer(renderer); @@ -241,23 +271,23 @@ class xGLSurfaceView extends GLSurfaceView { case MotionEvent.ACTION_UP: t=4; break; case MotionEvent.ACTION_POINTER_UP: t=4; break; } - if (t>0) { + if (t>0) { final int n=event.getPointerCount(); final int t0=t; final int id0=event.getPointerId(0); final int x0=(int)event.getX(0); final int y0=(int)event.getY(0); if (n>1) { // MultiTouch - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id0,0); }}); + queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id0,0); }}); } - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x0,y0); }}); + queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x0,y0); }}); if (n>1) { // MultiTouch final int id1=event.getPointerId(1); final int x1=(int)event.getX(1); final int y1=(int)event.getY(1); - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id1,0); }}); - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x1,y1); }}); - } + queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id1,0); }}); + queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x1,y1); }}); + } } return true; } @@ -295,7 +325,7 @@ class xGLSurfaceView extends GLSurfaceView { } if (t>0) { queueEvent(new Runnable(){ public void run() { - renderer.nativeEvent(t,x,y); }}); + renderer.nativeEvent(t,x,y); }}); } return true; } @@ -311,15 +341,15 @@ class xGLSurfaceView extends GLSurfaceView { myRenderer renderer; } class myRenderer implements GLSurfaceView.Renderer { - public void onSurfaceCreated(GL10 gl, EGLConfig config) { + public void onSurfaceCreated(GL10 gl, EGLConfig config) { } public void onSurfaceChanged(GL10 gl, int w, int h) { gl.glViewport(0, 0, w, h); width=(float)w; height=(float)h; nativeEvent(127,w,h); // EVENT_INIT } - public void onDrawFrame(GL10 gl) { - nativeEvent(15,0,0); // EVENT_REDRAW + public void onDrawFrame(GL10 gl) { + nativeEvent(15,0,0); // EVENT_REDRAW } public void pointerEvent(int t, int x, int y) { nativeEvent(t,x,(int)height-y); } public float width,height; diff --git a/modules/camera/ANDROID_java_activityadditions b/modules/camera/ANDROID_java_activityadditions index 62d5242f..1e5f91de 100644 --- a/modules/camera/ANDROID_java_activityadditions +++ b/modules/camera/ANDROID_java_activityadditions @@ -14,7 +14,14 @@ private void startCamera(String fnl_name, String tmp_name) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File f = new File(camera_tmp); if (f != null) { - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); + Uri imageUri = FileProvider.getUriForFile(this, + "@SYS_PACKAGE_DOT@.provider", + f); + intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); + if (@SYS_ANDROIDAPI@ <= 22) { + intent.setClipData(ClipData.newRawUri("", imageUri)); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION); + } // intent.putExtra(android.provider.MediaStore.EXTRA_SCREEN_ORIENTATION,ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); startActivityForResult(intent, REQUEST_IMAGE_CAPTURE); } @@ -26,7 +33,14 @@ private void startVidCamera(String fnl_name, String tmp_name, int maxlength) { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); File f = new File(vid_tmp); if (f != null) { - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); + Uri vidUri = FileProvider.getUriForFile(this, + "@SYS_PACKAGE_DOT@.provider", + f); + intent.putExtra(MediaStore.EXTRA_OUTPUT, vidUri); + if (@SYS_ANDROIDAPI@ <= 22) { + intent.setClipData(ClipData.newRawUri("", vidUri)); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION); + } if (maxlength > 0) { intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, maxlength); } diff --git a/modules/camera/ANDROID_java_imports b/modules/camera/ANDROID_java_imports index abfaef12..0712cc5c 100644 --- a/modules/camera/ANDROID_java_imports +++ b/modules/camera/ANDROID_java_imports @@ -10,4 +10,6 @@ import android.media.ExifInterface; import java.io.FileOutputStream; import java.io.IOException; +import android.support.v4.content.FileProvider; + diff --git a/modules/config/config.scm b/modules/config/config.scm index 6880f1da..9cd6eaea 100644 --- a/modules/config/config.scm +++ b/modules/config/config.scm @@ -42,9 +42,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (c-declare #< (length event:fifo) 0) - (let ((ret (car event:fifo))) - (set! event:fifo (cdr event:fifo)) ret) #f)) + (if (null? event:fifo) #f + (let ((ret (car event:fifo))) + (set! event:fifo (cdr event:fifo)) ret))) + +(define on-jscm-result + (let ((mux (make-mutex 'on-jscm-result))) + (mutex-specific-set! mux #f) + (lambda args + (cond + ((null? args) ;; return receiver procedure + (lambda (t x y) + (let ((proc (mutex-specific mux))) + (when proc + (mutex-specific-set! mux #f) + (proc t x y) + (mutex-unlock! mux))))) + ((let ((proc (car args))) (and (procedure? proc) proc)) => + ;; set `proc` as inner receiver + (lambda (proc) + (mutex-lock! mux) + (mutex-specific-set! mux proc) + #t)) + (else (log-error "illegal arguments" on-jscm-result args)))))) (define eventloop:mutex (make-mutex 'eventloop)) (define (eventloop:grab!) (mutex-lock! eventloop:mutex)) @@ -179,7 +200,8 @@ end-of-c-declare ;; handle potential scaling (running stretched on a device) (hook:event t (if app:scale? (fix (* app:xscale x)) x) (if app:scale? (fix (* app:yscale y)) y)) - ) + ) + ((fx= t EVENT_JSCM_RESULT) ((on-jscm-result) t x y)) ((fx= t EVENT_INIT) ;; prevent multiple inits (if app:mustinit (begin diff --git a/modules/ln_core/log.scm b/modules/ln_core/log.scm index fd15ace2..5e42f233 100644 --- a/modules/ln_core/log.scm +++ b/modules/ln_core/log.scm @@ -37,6 +37,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |# ;; logger +(c-declare #< +end-of-c-declare +) + ;; we are logging from different threads (define log:mutex (make-mutex 'log)) (define (log:grab!) (mutex-lock! log:mutex)) @@ -119,31 +124,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (log:release!) ))) -;; try to output location of continuation -;; this only works if debug information is available -(define (trace:identify cont) - (let ((locat (##continuation-locat cont))) - (if locat - (let* ((container (##locat-container locat)) - (path (##container->path container))) - (if path - (let* ((filepos (##position->filepos (##locat-position locat))) - (line (fx+ (##filepos-line filepos) 1)) - (col (fx+ (##filepos-col filepos) 1))) - (log-error "trace: " path " line=" line " col=" col)) - #f)) - #f))) - -(define (log-trace thread) - (let* ((capture (##thread-continuation-capture thread))) - (let loop ((cont (##continuation-first-frame capture #f))(n 0)) - (if cont (begin - (if (> n 1) (trace:identify cont)) - (loop (##continuation-next-frame cont #f)(fx+ n 1)) - )) - ) - )) - (define (exception->string e) (let* ((str (with-output-to-string '() (lambda () (display-exception e (current-output-port))))) (tmp (string-split str #\newline))) @@ -151,14 +131,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (define (log:exception-handler e) (log-error "Thread \"" (thread-name (current-thread)) "\": " (exception->string e)) - (cond-expand - (gambit-c (log-trace (current-thread))) - (else - (unless (deadlock-exception? e) - ;; gambit ___cleanup(); re-enters with a deadlock-exception here - ;; while printing the trace - (log-trace (current-thread))) - )) + (log-error + (call-with-output-string + '() + (lambda (port) + (continuation-capture + (lambda (cont) + (display-exception-in-context e cont port) + (display-continuation-backtrace cont port)))))) (log-error "HALT pid " ((c-lambda () int "getpid"))) (exit 70)) diff --git a/modules/ln_glcore/glcore.scm b/modules/ln_glcore/glcore.scm index 60fd9d77..4b030d89 100644 --- a/modules/ln_glcore/glcore.scm +++ b/modules/ln_glcore/glcore.scm @@ -1,6 +1,6 @@ #| LambdaNative - a cross-platform Scheme framework -Copyright (c) 2009-2013, University of British Columbia +Copyright (c) 2009-2020, University of British Columbia All rights reserved. Redistribution and use in source and binary forms, with or @@ -131,44 +131,54 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ) (define (glCoreVertex2f x0 y0 . xtra) - (let ((x (flo x0)) (y (flo y0)) - (tx (if (fx= (length xtra) 2) (flo (car xtra)) 0.5)) - (ty (if (fx= (length xtra) 2) (flo (cadr xtra)) 0.5))) - (f32vector-set! glCore:varray (fx+ glCore:vindex 0) x) - (f32vector-set! glCore:varray (fx+ glCore:vindex 1) y) - (set! glCore:vindex (fx+ glCore:vindex 2)) - (f32vector-set! glCore:tarray (fx+ glCore:tindex 0) tx) - (f32vector-set! glCore:tarray (fx+ glCore:tindex 1) ty) - (set! glCore:tindex (fx+ glCore:tindex 2)) - (u8vector-set! glCore:carray (fx+ glCore:cindex 0) glCore:red) - (u8vector-set! glCore:carray (fx+ glCore:cindex 1) glCore:green) - (u8vector-set! glCore:carray (fx+ glCore:cindex 2) glCore:blue) - (u8vector-set! glCore:carray (fx+ glCore:cindex 3) glCore:alpha) - (set! glCore:cindex (fx+ glCore:cindex 4)) - (set! glCore:use3D #f) - )) + (let* ((txx (pair? xtra)) + (tx (if txx (flo (car xtra)) 0.5)) + (ty (cond + ((not txx) 0.5) + ((let ((r (cdr xtra))) + (and (pair? r) (car r)))) + (else 0.5)))) + (let ((x (flo x0)) (y (flo y0))) + (f32vector-set! glCore:varray (fx+ glCore:vindex 0) x) + (f32vector-set! glCore:varray (fx+ glCore:vindex 1) y) + (set! glCore:vindex (fx+ glCore:vindex 2)) + (f32vector-set! glCore:tarray (fx+ glCore:tindex 0) tx) + (f32vector-set! glCore:tarray (fx+ glCore:tindex 1) ty) + (set! glCore:tindex (fx+ glCore:tindex 2)) + (u8vector-set! glCore:carray (fx+ glCore:cindex 0) glCore:red) + (u8vector-set! glCore:carray (fx+ glCore:cindex 1) glCore:green) + (u8vector-set! glCore:carray (fx+ glCore:cindex 2) glCore:blue) + (u8vector-set! glCore:carray (fx+ glCore:cindex 3) glCore:alpha) + (set! glCore:cindex (fx+ glCore:cindex 4)) + (set! glCore:use3D #f) + ))) ;; ------------------------------------------ ;; 3D rendering (define (glCoreVertex3f x0 y0 z0 . xtra) - (let ((x (flo x0)) (y (flo y0)) (z (flo z0)) - (tx (if (fx= (length xtra) 2) (flo (car xtra)) 0.5)) - (ty (if (fx= (length xtra) 2) (flo (cadr xtra)) 0.5))) - (f32vector-set! glCore:varray3D (fx+ glCore:vindex 0) x) - (f32vector-set! glCore:varray3D (fx+ glCore:vindex 1) y) - (f32vector-set! glCore:varray3D (fx+ glCore:vindex 2) z) - (set! glCore:vindex (fx+ glCore:vindex 3)) - (f32vector-set! glCore:tarray (fx+ glCore:tindex 0) tx) - (f32vector-set! glCore:tarray (fx+ glCore:tindex 1) ty) - (set! glCore:tindex (fx+ glCore:tindex 2)) - (u8vector-set! glCore:carray (fx+ glCore:cindex 0) glCore:red) - (u8vector-set! glCore:carray (fx+ glCore:cindex 1) glCore:green) - (u8vector-set! glCore:carray (fx+ glCore:cindex 2) glCore:blue) - (u8vector-set! glCore:carray (fx+ glCore:cindex 3) glCore:alpha) - (set! glCore:cindex (fx+ glCore:cindex 4)) - (set! glCore:use3D #t) - )) + (let* ((txx (pair? xtra)) + (tx (if txx (flo (car xtra)) 0.5)) + (ty (cond + ((not txx) 0.5) + ((let ((r (cdr xtra))) + (and (pair? r) (car r)))) + (else 0.5)))) + (let ((x (flo x0)) (y (flo y0)) (z (flo z0))) + (f32vector-set! glCore:varray3D (fx+ glCore:vindex 0) x) + (f32vector-set! glCore:varray3D (fx+ glCore:vindex 1) y) + (f32vector-set! glCore:varray3D (fx+ glCore:vindex 2) z) + (set! glCore:vindex (fx+ glCore:vindex 3)) + (f32vector-set! glCore:tarray (fx+ glCore:tindex 0) tx) + (f32vector-set! glCore:tarray (fx+ glCore:tindex 1) ty) + (set! glCore:tindex (fx+ glCore:tindex 2)) + (u8vector-set! glCore:carray (fx+ glCore:cindex 0) glCore:red) + (u8vector-set! glCore:carray (fx+ glCore:cindex 1) glCore:green) + (u8vector-set! glCore:carray (fx+ glCore:cindex 2) glCore:blue) + (u8vector-set! glCore:carray (fx+ glCore:cindex 3) glCore:alpha) + (set! glCore:cindex (fx+ glCore:cindex 4)) + (set! glCore:use3D #t) + ))) ;; ---------------------------------- ;; textures @@ -180,18 +190,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (define (glCoreTextureCreate w h data . aux) (glcore:log 5 "glCoreTextureCreate") - (let ((idx glCore:tidx) - (pixeltype (cond - ((fx= (u8vector-length data) (* w h)) GL_ALPHA) - ((fx= (u8vector-length data) (* 3 w h)) GL_RGB) - ((fx= (u8vector-length data) (* 4 w h)) GL_RGBA) - (else (log-error "glCoreTextureCreate: Invalid data range") #f))) - (interpolation (if (>= (length aux) 1) (car aux) GL_LINEAR)) - (wrap (if (>= (length aux) 2) (cadr aux) GL_CLAMP))) - (table-set! glCore:textures idx (##still-copy - (vector #f (u32vector 0) w h (##still-copy data) pixeltype interpolation wrap))) - (set! glCore:tidx (fx+ glCore:tidx 1)) - idx)) + (let* ((o1x (pair? aux)) + (o2 (and o1x (cdr aux)))) + (let ((idx glCore:tidx) + (pixeltype + (cond + ((fx= (u8vector-length data) (* w h)) GL_ALPHA) + ((fx= (u8vector-length data) (* 3 w h)) GL_RGB) + ((fx= (u8vector-length data) (* 4 w h)) GL_RGBA) + (else (log-error "glCoreTextureCreate: Invalid data range") #f))) + (interpolation (if o1x (car aux) GL_LINEAR)) + (wrap (if (pair? o2) (car o2) GL_CLAMP))) + (table-set! glCore:textures idx + (##still-copy (vector #f (u32vector 0) w h (##still-copy data) pixeltype interpolation wrap))) + (set! glCore:tidx (fx+ glCore:tidx 1)) + idx))) ;; return texture width (define (glCoreTextureWidth t) @@ -226,19 +239,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;; (glCoreClipPush x1 y1 x2 y2) (define (glCoreClipPush . coords) (let* ((oldlist glcore:cliplist) - (newcoords (if (fx= (length coords) 4) (map flo - (list (min (car coords) (caddr coords)) (min (cadr coords) (cadddr coords)) - (max (car coords) (caddr coords)) (max (cadr coords) (cadddr coords)))) #f)) - (newlist (if newcoords (append (list newcoords) oldlist) - (if (null? oldlist) oldlist (cdr oldlist))))) - (if (not (null? newlist)) (begin - (set! glcore:clipx1 (car (car newlist))) - (set! glcore:clipy1 (cadr (car newlist))) - (set! glcore:clipx2 (caddr (car newlist))) - (set! glcore:clipy2 (cadddr (car newlist))) - )) - (set! glcore:cliplist newlist) - )) + (newcoords + (if (fx= (length coords) 4) + (map flo + (list (min (car coords) (caddr coords)) + (min (cadr coords) (cadddr coords)) + (max (car coords) (caddr coords)) + (max (cadr coords) (cadddr coords)))) + #f)) + (newlist (if newcoords + (append (list newcoords) oldlist) + (if (null? oldlist) oldlist (cdr oldlist))))) + (if (not (null? newlist)) + (begin + (set! glcore:clipx1 (car (car newlist))) + (set! glcore:clipy1 (cadr (car newlist))) + (set! glcore:clipx2 (caddr (car newlist))) + (set! glcore:clipy2 (cadddr (car newlist))))) + (set! glcore:cliplist newlist))) (define glCoreClipPop glCoreClipPush) @@ -252,13 +270,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (if entry (let ((w (flo (if (fx= (fix w0) 0) (vector-ref entry 2) w0))) (h (flo (if (fx= (fix h0) 0) (vector-ref entry 3) h0)))) - (if (null? glcore:cliplist) - (apply glCore:TextureDrawUnClipped (append (list (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r)) - (if (null? colors) '() (car colors)))) - (apply glCore:TextureDrawClipped (append (list (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r)) - (if (null? colors) '() (car colors))))) - ) (log-error "glCoreTextureDraw: unbound index " t)) - )) + (if (null? glcore:cliplist) + (if (pair? colors) + (glCore:TextureDrawUnClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) + (car colors)) + (glCore:TextureDrawUnClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))) + (if (pair? colors) + (glCore:TextureDrawClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) + (car colors)) + (glCore:TextureDrawClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))))) + (log-error "glCoreTextureDraw: unbound index " t)))) (define (glCore:TextureDrawUnClipped x y w h t @x1 @y1 @x2 @y2 r . colors) (glcore:log 5 "glCoreTextureDrawUnclipped enter") @@ -273,15 +298,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (glCoreVertex2f w2 h2 @x2 @y2) (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) (glCoreVertex2f w2 (fl- h2) @x2 @y1) - )(begin - (glCoreColor (car colors)) - (glCoreVertex2f (fl- w2) h2 @x1 @y2) - (glCoreColor (cadr colors)) - (glCoreVertex2f w2 h2 @x2 @y2) - (glCoreColor (caddr colors)) - (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) - (glCoreColor (cadddr colors)) - (glCoreVertex2f w2 (fl- h2) @x2 @y1) + )(let ((colors (list->vector (car colors)))) + (glCoreColor (vector-ref colors 0)) + (glCoreVertex2f (fl- w2) h2 @x1 @y2) + (glCoreColor (vector-ref colors 1)) + (glCoreVertex2f w2 h2 @x2 @y2) + (glCoreColor (vector-ref colors 2)) + (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) + (glCoreColor (vector-ref colors 3)) + (glCoreVertex2f w2 (fl- h2) @x2 @y1) )) (glCoreEnd) (glPopMatrix) @@ -309,22 +334,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (glRotatef r 0. 0. 1.) (_glCoreTextureBind t) (glCoreBegin GL_TRIANGLE_STRIP) - (if (null? colors) (begin - (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) - (glCoreVertex2f cw2 ch2 c@x2 c@y2) - (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) - (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) - ) (begin - ;; TODO: color interpolation here! - (glCoreColor (car colors)) - (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) - (glCoreColor (cadr colors)) - (glCoreVertex2f cw2 ch2 c@x2 c@y2) - (glCoreColor (caddr colors)) - (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) - (glCoreColor (cadddr colors)) - (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) - )) + (if (null? colors) + (begin + (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) + (glCoreVertex2f cw2 ch2 c@x2 c@y2) + (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) + (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) + ) + (let ((colors (list->vector (car colors)))) + ;; TODO: color interpolation here! + (glCoreColor (vector-ref colors 0)) + (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) + (glCoreColor (vector-ref colors 1)) + (glCoreVertex2f cw2 ch2 c@x2 c@y2) + (glCoreColor (vector-ref colors 2)) + (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) + (glCoreColor (vector-ref colors 3)) + (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) + )) (glCoreEnd) (glPopMatrix) ))) @@ -338,24 +365,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (glcore:log 5 "glCoreTexturePolygonDraw") (let ((entry (table-ref glCore:textures t #f))) (if entry - (let* ((cx (flo _cx)) (cy (flo _cy)) - (r (flo _r))) + (let* ((cx (flo _cx)) (cy (flo _cy)) (r (flo _r))) (glPushMatrix) (glTranslatef cx cy 0.) (glRotatef r 0. 0. 1.) (_glCoreTextureBind t) (glCoreBegin GL_TRIANGLE_STRIP) - (for-each (lambda (p) - (let* ((x (fl- (car p) cx)) - (y (fl- (cadr p) cy)) - (tx (caddr p)) - (ty (cadddr p))) + (for-each + (lambda (p) + ;; TBD: should accept vectoralikes as point + (let* ((p (list->vector p)) + (x (fl- (vector-ref p 0) cx)) + (y (fl- (vector-ref p 1) cy)) + (tx (vector-ref p 2)) + (ty (vector-ref p 3))) (glCoreVertex2f x y tx ty))) - points) - (glCoreEnd) - (glPopMatrix) - ) (log-error "glCoreTexturePolygonDraw: unbound index " t)) - )) + points) + (glCoreEnd) + (glPopMatrix)) + (log-error "glCoreTexturePolygonDraw: unbound index " t)))) ;; update texture data (for dynamic textures) ;; to use this, first modify data returned with glCoreTextureData.. diff --git a/modules/lnjscheme/ANDROID_c_additions b/modules/lnjscheme/ANDROID_c_additions new file mode 100644 index 00000000..f7c9a626 --- /dev/null +++ b/modules/lnjscheme/ANDROID_c_additions @@ -0,0 +1,82 @@ +/* -*-C-*- */ + +const char* android_app_class() { return "@SYS_PACKAGE_DOT@.@SYS_APPNAME@"; } // for jscheme + +/* lnjscheme_eval + * + * Evaluate input and return result. Due to Android limitations + * wrt. thread and evaluation context, calls might fail. E.g., Views + * may only be changed by the Java thread which created them. Use the + * asynchronous version in those cases. + */ +const char* lnjscheme_eval(const char* input) +{ + static const char *str = NULL; + static jstring jstr = NULL; + JNIEnv *env = GetJNIEnv(); + if (env&&globalObj){ + jstring jin = (*env)->NewStringUTF(env,input); + jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); + jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeCall", "(Ljava/lang/String;)Ljava/lang/String;") : NULL; + if(main_class) (*env)->DeleteLocalRef(env, main_class); + if(!method) { + JNI_forward_exception_to_gambit(env); + return "E \"JNI: method LNjSchemeCall not found\""; + } + if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } + jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jin); + (*env)->DeleteLocalRef(env, method); + (*env)->DeleteLocalRef(env, jin); + str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; + // (*env)->ReleaseStringUTFChars(env, jstr, str); // we do it upon next call + JNI_forward_exception_to_gambit(env); + } + return str; +} + +void lnjscheme_eval_send(const char* input) +{ + JNIEnv *env = GetJNIEnv(); + if (env&&globalObj){ + jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); + jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeSend", "(Ljava/lang/String;)V") : NULL; + if(main_class) (*env)->DeleteLocalRef(env, main_class); + if(!method) { + JNI_forward_exception_to_gambit(env); + return; // "E \"JNI: method LNjSchemeSend not found\""; + } else { + jstring jin = (*env)->NewStringUTF(env,input); + (*env)->CallVoidMethod(env, globalObj, method, jin); + (*env)->DeleteLocalRef(env, method); + (*env)->DeleteLocalRef(env, jin); + JNI_forward_exception_to_gambit(env); + } + } +} + +// There is likely a way to do this better using only a Java->C call +// to deposit the result in a global variable. I just don't know yet +// how to do this. +const char* lnjscheme_eval_receive_result() +{ + static const char *str = NULL; + static jstring jstr = NULL; + JNIEnv *env = GetJNIEnv(); + if (env&&globalObj){ + if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } + jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); + jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeResult", "()Ljava/lang/String;") : NULL; + if(main_class) (*env)->DeleteLocalRef(env, main_class); + if(!method) { + JNI_forward_exception_to_gambit(env); + return "E \"JNI: method LNjSchemeResult not found\""; + } else { + jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method); + str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; + // (*env)->ReleaseStringUTFChars(env, jstr, str); // we do it upon next call + (*env)->DeleteLocalRef(env, method); + JNI_forward_exception_to_gambit(env); + } + } + return str; +} diff --git a/modules/lnjscheme/ANDROID_java_activityadditions b/modules/lnjscheme/ANDROID_java_activityadditions new file mode 100644 index 00000000..d8a64863 --- /dev/null +++ b/modules/lnjscheme/ANDROID_java_activityadditions @@ -0,0 +1,160 @@ +/* LNjScheme -*- mode: java; c-basic-offset: 2; -*- */ + +/* # Helper methods */ + +java.text.SimpleDateFormat ln_log_date_formatter = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss "); + +String TAG = "@SYS_APPNAME@"; + +public void ln_log(String msg) { + String m = ln_log_date_formatter.format(new java.util.Date()) + msg; + System.err.println(TAG + ": " + m); + Log.d(TAG, m); +} + +private Object onBackPressedHandler = null; + +@Override +public void onBackPressed() { + if(onBackPressedHandler!=null) { + LNjSchemeEvaluate(LNjScheme.Scheme.list(onBackPressedHandler)); + } + else { super.onBackPressed(); } +} + + +/* LNjScheme_Set_OnClickListener: register a LNjScheme lambda to be called when the View is clicked. + * + * LNjScheme can not (yet) declare annonymous derived classes. (Or at + * least I don't know how that could be done.) + * + * For the time being we get along with a little Java. + */ +private android.view.View.OnClickListener LNjScheme_OnClickListener(final Object proc) { + return new android.view.View.OnClickListener() { + public void onClick(android.view.View v) { + LNjSchemeEvaluate(new LNjScheme.Pair(proc, new LNjScheme.Pair(v, null))); + } + }; +} +public void LNjScheme_Set_OnClickListener(android.view.View v, Object expr) { + v.setOnClickListener(LNjScheme_OnClickListener(expr)); +} + +/* # LNjScheme core */ + +private static LNjScheme.Scheme LNjSchemeSession = null; + +private Object LNjSchemeEvaluateNoSync(Object expr) { + // NOT synchronized, be careful where to use it! + if(LNjSchemeSession != null) { + return LNjSchemeSession.eval(expr); + } else return null; +} + +public Object LNjSchemeEvaluate(Object expr) { + // sync with the one and only evaluator supported so far. + if(LNjSchemeSession != null) { + synchronized(LNjSchemeSession) { + return LNjSchemeSession.eval(expr); + } + } else return null; +} + +/* jschemeCall: evaluate `msg` in any Java thread and return result + * + * FIXME TBD CHECK: This was the initial implementation, but might be broken now. + */ +public String LNjSchemeCall(String msg) { + // BEWARE: Operations not safe to be called asynchronously from + // any thread, not safe to be called from various contexts (e.g., + // within "onDrawFrame" which amounts to "while reacting to + // EVENT_REDRAW"), etc. MAY HANG here. + // + // If you need fast execution and know the call is safe use this + // one. Otherwise use the two-phased version using + // `LNjSchemeSend` followed by a `LNjSchemeResult to dispatch the + // evaluation to `runOnUiThread` and wait for it to be eventually + // evaluated in a more-or-less safe context. + try { + LNjScheme.InputPort in = new LNjScheme.InputPort(new java.io.ByteArrayInputStream(msg.getBytes(java.nio.charset.Charset.forName("UTF-8")))); + Object expr = in.read(); + if(in.isEOF(expr)) return "E\n\"invalid input\""; + Object result = LNjSchemeEvaluate(expr); + java.io.StringWriter buf = new java.io.StringWriter(); + java.io.PrintWriter port = new java.io.PrintWriter(buf); + port.println("D"); + LNjScheme.SchemeUtils.write(result, port, true); + return buf.toString(); + } catch (Exception e) { + java.io.StringWriter buf = new java.io.StringWriter(); + java.io.PrintWriter port = new java.io.PrintWriter(buf); + port.println("E"); + LNjScheme.SchemeUtils.write(("" + e).toCharArray(), port, true); + return buf.toString(); + } +} + +/* LNjSchemeSend: send string for evaluation to Java app main thread. + * + * LNjSchemeResult: receive evaluation result from Java app main thread. + */ + +private java.util.concurrent.FutureTask LNjSchemeJob = null; + +public void LNjSchemeSend(String msg) { + final LNjScheme.InputPort in = new LNjScheme.InputPort(new java.io.ByteArrayInputStream(msg.getBytes(java.nio.charset.Charset.forName("UTF-8")))); + final Object expr = in.read(); + // Object result = LNjSchemeEvaluate(expr); + java.util.concurrent.FutureTask job = new java.util.concurrent.FutureTask + (new java.util.concurrent.Callable() { + @Override + public Object call() throws Exception { + // ln_log("invocation of " + this + " evaluating."); + if(in.isEOF(expr)) throw new Exception("invalid input"); + return LNjSchemeEvaluate(expr); } + }); + // ln_log("Sending to UI: " + job + " for: " + expr); + LNjSchemeJob = job; + new Thread() { + @Override + public void run() { + // ln_log("LNjScheme waiting for completion"); + try { + LNjSchemeJob.get(); + } catch (Exception e) { // InterruptedException java.util.concurrent.ExecutionException + // FIXME: Do something sensible here! + } + // ln_log("LNjScheme notifying result"); + nativeEvent(126,0,0); + } + }.start(); + runOnUiThread(job); +} + +public String LNjSchemeResult() { + try { + Object result = LNjSchemeJob != null ? LNjSchemeJob.get() : null; + LNjSchemeJob = null; + // ln_log("got result from UI"); + java.io.StringWriter buf = new java.io.StringWriter(); + java.io.PrintWriter port = new java.io.PrintWriter(buf); + port.println("D"); + LNjScheme.SchemeUtils.write(result, port, true); + return buf.toString(); + } catch (java.util.concurrent.ExecutionException e) { + // ln_log("got error from call"); + java.io.StringWriter buf = new java.io.StringWriter(); + java.io.PrintWriter port = new java.io.PrintWriter(buf); + port.println("E"); + LNjScheme.SchemeUtils.write(("" + e.getCause()).toCharArray(), port, true); + return buf.toString(); + } catch (Exception e) { + // ln_log("got exception from call"); + java.io.StringWriter buf = new java.io.StringWriter(); + java.io.PrintWriter port = new java.io.PrintWriter(buf); + port.println("E"); + LNjScheme.SchemeUtils.write(("LNjScheme unexpected exception: " + e).toCharArray(), port, true); + return buf.toString(); + } +} diff --git a/modules/lnjscheme/ANDROID_java_additions b/modules/lnjscheme/ANDROID_java_additions new file mode 100644 index 00000000..5dbaa8b7 --- /dev/null +++ b/modules/lnjscheme/ANDROID_java_additions @@ -0,0 +1,16 @@ +/*-*-java -*-*/ +class LNMethod extends LNjScheme.Procedure { + + String name = null; + + /** Make a method from an exported wrapper, body, and environment. **/ + public LNMethod (String sym) { + name = sym; + } + + + /** Apply to a list of arguments. **/ + public Object apply(LNjScheme.Scheme interpreter, Object args) { + return null; // return interpreter.eval(body, new Environment(parms, args, env)); + } +} diff --git a/modules/lnjscheme/ANDROID_java_oncreate b/modules/lnjscheme/ANDROID_java_oncreate new file mode 100644 index 00000000..ce901cb4 --- /dev/null +++ b/modules/lnjscheme/ANDROID_java_oncreate @@ -0,0 +1,126 @@ +/* LNjScheme -*- mode: java; c-basic-offset: 2; -*- */ + +LNjSchemeSession = new LNjScheme.Scheme + (new String[0]) + { + String TAG = "calculator"; + + public void ln_log(String msg) { + String m = ln_log_date_formatter.format(new java.util.Date()) + msg; + System.err.println(TAG + ": " + m); + Log.d(TAG, m); + } + }; + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("ln-this"), + this + ))); + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("ln-mglview"), + mGLView + ))); + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("log-message"), + new LNMethod("log-message") { + public Object apply(LNjScheme.Scheme interpreter, Object args) { + String str = null; + if(args instanceof LNjScheme.Pair) { + Object a1 = null; + a1 = LNjScheme.Scheme.first(args); + if(a1 instanceof String) { str = (String)a1; } + else if(a1 instanceof char[]) { str = new String((char[])a1); } + else { str = "log-message: message not convertible"; } + } else { + str = "log-message: args not a list"; + } + ln_log(str); + return null; + }} + ))); + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("bound?"), + new LNMethod("bound?") { + public Object apply(LNjScheme.Scheme interpreter, Object args) { + if(args instanceof LNjScheme.Pair) { + Object a1 = null; + a1 = LNjScheme.Scheme.first(args); + if(a1 instanceof String) { + String sym = (String)a1; + try { + Object val = interpreter.eval(sym); + return true; + } catch (RuntimeException e) { return false; } + } else { + return LNjScheme.Scheme.error("bound? : not a symbol " + a1); + } + } else { + return LNjScheme.Scheme.error("bound? : missing argument"); + } + }} + ))); + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("send-event!"), + new LNMethod("send-event!") { + public Object apply(LNjScheme.Scheme interpreter, Object args) { + String str = null; + if(args instanceof LNjScheme.Pair) { + Object a1 = null, a2 = null, a3 = null;; + a1 = LNjScheme.Scheme.first(args); + a2 = LNjScheme.Scheme.rest(args); + a3 = LNjScheme.Scheme.rest(a2); + a2 = LNjScheme.Scheme.first(a2); + a3 = LNjScheme.Scheme.first(a3); + // Maybe we should accept symbolic event names too? + int ia1 = (a1 instanceof Number) ? (int)LNjScheme.Scheme.num(a1) : 21; + int ia2 = (a2 instanceof Number) ? (int)LNjScheme.Scheme.num(a2) : 0; + int ia3 = (a3 instanceof Number) ? (int)LNjScheme.Scheme.num(a3) : 0; + nativeEvent(ia1, ia2, ia3); + return LNjScheme.Scheme.TRUE; + } else { + nativeEvent(64, 0, 0); // debug + return LNjScheme.Scheme.TRUE; + } + }} + ))); + +LNjSchemeEvaluateNoSync + (LNjScheme.Scheme.cons + (LNjScheme.Scheme.sym("define"), + LNjScheme.Scheme.list + (LNjScheme.Scheme.sym("on-back-pressed"), + new LNMethod("on-back-pressed") { + public Object apply(LNjScheme.Scheme interpreter, Object args) { + String str = null; + if(args instanceof LNjScheme.Pair) { + Object a1 = null; + a1 = LNjScheme.Scheme.first(args); + if(a1 instanceof LNjScheme.Procedure) { onBackPressedHandler = (LNjScheme.Procedure)a1; } + else if(!LNjScheme.Scheme.truth(a1)) { onBackPressedHandler = null; } + else { LNjScheme.Scheme.error("on-back-pressed: argument not a procedure or #f"); } + return LNjScheme.Scheme.TRUE; + } else { + if(onBackPressedHandler==null) { return LNjScheme.Scheme.FALSE; } + else { return onBackPressedHandler; } + } + }} + ))); diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Closure.java b/modules/lnjscheme/LNjScheme/Closure.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Closure.java rename to modules/lnjscheme/LNjScheme/Closure.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Continuation.java b/modules/lnjscheme/LNjScheme/Continuation.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Continuation.java rename to modules/lnjscheme/LNjScheme/Continuation.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Environment.java b/modules/lnjscheme/LNjScheme/Environment.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Environment.java rename to modules/lnjscheme/LNjScheme/Environment.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/InputPort.java b/modules/lnjscheme/LNjScheme/InputPort.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/InputPort.java rename to modules/lnjscheme/LNjScheme/InputPort.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/JavaMethod.java b/modules/lnjscheme/LNjScheme/JavaMethod.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/JavaMethod.java rename to modules/lnjscheme/LNjScheme/JavaMethod.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Macro.java b/modules/lnjscheme/LNjScheme/Macro.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Macro.java rename to modules/lnjscheme/LNjScheme/Macro.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Pair.java b/modules/lnjscheme/LNjScheme/Pair.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Pair.java rename to modules/lnjscheme/LNjScheme/Pair.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Primitive.java b/modules/lnjscheme/LNjScheme/Primitive.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Primitive.java rename to modules/lnjscheme/LNjScheme/Primitive.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Procedure.java b/modules/lnjscheme/LNjScheme/Procedure.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Procedure.java rename to modules/lnjscheme/LNjScheme/Procedure.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/Scheme.java b/modules/lnjscheme/LNjScheme/Scheme.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/Scheme.java rename to modules/lnjscheme/LNjScheme/Scheme.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/SchemePrimitives.java b/modules/lnjscheme/LNjScheme/SchemePrimitives.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/SchemePrimitives.java rename to modules/lnjscheme/LNjScheme/SchemePrimitives.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/SchemeUtils.java b/modules/lnjscheme/LNjScheme/SchemeUtils.java similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/SchemeUtils.java rename to modules/lnjscheme/LNjScheme/SchemeUtils.java diff --git a/apps/DemoAndroidLNjScheme/LNjScheme/primitives.scm b/modules/lnjscheme/LNjScheme/primitives.scm similarity index 100% rename from apps/DemoAndroidLNjScheme/LNjScheme/primitives.scm rename to modules/lnjscheme/LNjScheme/primitives.scm diff --git a/modules/lnjscheme/MODULES b/modules/lnjscheme/MODULES new file mode 100644 index 00000000..63d57220 --- /dev/null +++ b/modules/lnjscheme/MODULES @@ -0,0 +1 @@ +config eventloop diff --git a/apps/DemoAndroidLNjScheme/Makefile b/modules/lnjscheme/Makefile similarity index 100% rename from apps/DemoAndroidLNjScheme/Makefile rename to modules/lnjscheme/Makefile diff --git a/apps/DemoAndroidLNjScheme/README.md b/modules/lnjscheme/README.md similarity index 58% rename from apps/DemoAndroidLNjScheme/README.md rename to modules/lnjscheme/README.md index e35bb084..7e429264 100644 --- a/apps/DemoAndroidLNjScheme/README.md +++ b/modules/lnjscheme/README.md @@ -4,31 +4,11 @@ This directory contains an app to demo how to use LNjScheme from LN. LNjScheme allows to call any Java/Android method from lambdanative/gambit without additional JNI code. Either directly or -within tje UI thread (dispatched asynchronously via `runOnUiThread`). +within the UI thread (dispatched asynchronously via `runOnUiThread`). ## Build -1. call `make -f Makefile` in this directory to create `android_jars/LNjScheme.jar`. -2. use lambdanative make to create the demo app. - -## Toy With It - -To the user, the interesting file is `lnjstest.scm`. It contains user -defined code to be run. - -- An example file `lnjstest.scm` is embedded. Modify it to suit your - likings. -- use `adb push lnjstest.scm /scdard/DemoAndroidLNjScheme/` to install -- push the button *Load it!* to execute it from the demo app. - -The (initial) example `lnjstest.scm` replaces the content of the app -with a `LinearLayout` containing a (scaled, since I did not find out -how to resize it) view of the content followed by a greating, a `Back` -button and a WebView displaying `lambdanative.org`. Push the button -to return to the previews view. - -NB: Garbage in `lnjstest.scm` will likely break the app. Use Androids -App Settings for *force terminate* then. +call `make -f Makefile` in this directory to create `android_jars/LNjScheme.jar`. # History @@ -61,21 +41,6 @@ LNjScheme and added Makefile. # Issues -## Split Into Module and Demo App - -The LNjScheme core stuff would better be a reusable module as it might -simplify the build script dance around e.g. `hybridapp`s. -Issues/missing: -1. how to compile the `.jar` during build -2. install the `.jar` -3. handle the EVENT_LNjSchemeRETURN (i.e. #126) in core (see - `main.scm` around line 127). -4. discover why exactly `##thread-heartbeat` of `gambit` fame was - disabled and clean up such that Gambit threads still work as - naively expected. (Maybe this solve the performance issue - observed.) - - ## Numbers jScheme uses `lang.java.Double` for all numbers. This does not play diff --git a/modules/lnjscheme/lnjscheme.scm b/modules/lnjscheme/lnjscheme.scm new file mode 100644 index 00000000..2faddec0 --- /dev/null +++ b/modules/lnjscheme/lnjscheme.scm @@ -0,0 +1,100 @@ +(cond-expand + (android + (c-declare "extern const char* android_app_class();") + (define android-app-class (c-lambda () char-string "android_app_class"))) + (else (define (android-app-class) + (log-error "android-app-class: called in non-Android context") + "android-app-class"))) + +(define call-with-lnjscheme-result + ;; SIGNATURE (NAME S-EXPR #!optional (RECEIVER force)) + ;; + ;; S-EXPR: Scheme s-expression in JSCM dialect + ;; + ;; RECEIVER: 1-ary procedure signature copatible to `force` + ;; + ;; Note: On the RECEIVERs descrection the result could be `forced`ed + ;; with, e.g., exception handlers in place, another threads context. + ;; The default is to fail as early as possible: on the events + ;; reception. However delaying the failure into the RECEIVERs + ;; context has the advantage of more focused failure location while + ;; risking to mask failures in background processing. + ;; + ;; Ergo: fail immeditately when testing or in background. RECEIVER, + ;; if provided, may override. + (let () + (define jscheme-send + (c-lambda (char-string) void " +#ifdef ANDROID +extern void lnjscheme_eval_send(const char *); +lnjscheme_eval_send(___arg1); +#endif +")) + (define jscheme-receive + (c-lambda () char-string " +#ifdef ANDROID +extern const char *lnjscheme_eval_receive_result(); +#endif +___result= +#ifdef ANDROID +(char*) lnjscheme_eval_receive_result(); +#else +NULL; +#endif +")) + (define (jscheme-read-reply obj) + (if (string? obj) + (call-with-input-string + obj + (lambda (port) + (let* ((key (read port)) + (value + (with-exception-catcher + (lambda (exn) (raise (string-append "jscheme-call: unreadable result: " obj))) + (lambda () (read port))))) + (case key + ((D) value) + ((E) (raise value)) + (else (error "jscheme-call: unexpected reply " obj)))))) + (error "jscheme-call: unexpected reply " obj))) + (define (jscheme-refine-result obj) + (cond + ;; Numbers are always printed as inexacts by jscheme. + ((integer? obj) (inexact->exact obj)) + (else obj))) + (define (jscheme-call obj #!optional (receiver force)) + (cond-expand + (android) + (else (log-error "jscheme-call: not availible on platform" (system-platform)))) + (on-jscm-result + (lambda (t x y) + (let* ((reply (jscheme-receive)) ;; extract the result from Java + ;; delay evalutation + (promise (delay (jscheme-refine-result (jscheme-read-reply reply))))) + ;; The optional receiver MAY either dispatch to + ;; asynchroneous forcing the promise catching exceptions + ;; etc. by default force it expection the application to + ;; abort on any exception. + (receiver promise)))) + (jscheme-send (object->string obj)) + (thread-yield!)) + jscheme-call)) + +(define (lnjscheme-future obj) + ;; a promise waiting for the evaluation of OBJ + (let ((result (make-mutex obj))) + (mutex-lock! result #f #f) + (call-with-lnjscheme-result + obj + (lambda (promise) + (mutex-specific-set! result promise) + (mutex-unlock! result))) + (delay + (begin + (mutex-lock! result #f #f) + (force (mutex-specific result)))))) + +(define (lnjscheme-eval obj) + ;; BEWARE: This blocks the current thread. WILL deadlock NOT when + ;; run in event handler thread. + (force (lnjscheme-future obj))) diff --git a/modules/serial/ANDROID_c_additions b/modules/serial/ANDROID_c_additions index 608106a8..327dcd76 100644 --- a/modules/serial/ANDROID_c_additions +++ b/modules/serial/ANDROID_c_additions @@ -82,7 +82,7 @@ void serial_writechar(int dev, int val){ JNIEnv *env = GetJNIEnv(); if (env&&serial_object) { serial_timeout_set(0); - (*env)->CallIntMethod(env,serial_object, s_serial_writechar, dev, val); + (*env)->CallVoidMethod(env,serial_object, s_serial_writechar, dev, val); } } int serial_readchar(int dev){ @@ -115,7 +115,7 @@ void serial_flush(int dev){ } JNIEnv *env = GetJNIEnv(); if (env&&serial_object) { - (*env)->CallIntMethod(env,serial_object, s_serial_flush, dev); + (*env)->CallVoidMethod(env,serial_object, s_serial_flush, dev); } } int serial_getDTR(int dev){ @@ -136,7 +136,7 @@ void serial_setDTR(int dev, int val){ } JNIEnv *env = GetJNIEnv(); if (env&&serial_object) { - (*env)->CallIntMethod(env,serial_object, s_serial_setdtr, dev, val); + (*env)->CallVoidMethod(env,serial_object, s_serial_setdtr, dev, val); } } int serial_getRTS(int dev){ @@ -157,7 +157,7 @@ void serial_setRTS(int dev, int val){ } JNIEnv *env = GetJNIEnv(); if (env&&serial_object) { - (*env)->CallIntMethod(env,serial_object, s_serial_setrts, dev, val); + (*env)->CallVoidMethod(env,serial_object, s_serial_setrts, dev, val); } } int serial_openfile(char *filepath){ diff --git a/modules/videoplayer/ANDROID_java_activityadditions b/modules/videoplayer/ANDROID_java_activityadditions index ac68f787..17f061a2 100644 --- a/modules/videoplayer/ANDROID_java_activityadditions +++ b/modules/videoplayer/ANDROID_java_activityadditions @@ -2,8 +2,12 @@ private void startVideoPlayer(String mov_name, int orientation) { File f = new File(mov_name); if (f != null) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.fromFile(f)); - intent.setDataAndType(Uri.fromFile(f), "video/*"); + Uri vidUri = FileProvider.getUriForFile(this, + "@SYS_PACKAGE_DOT@.provider", + f); + Intent intent = new Intent(Intent.ACTION_VIEW, vidUri); + intent.setDataAndType(vidUri, "video/*"); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (orientation == 1) { orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if (orientation == 2) { diff --git a/modules/videoplayer/ANDROID_java_imports b/modules/videoplayer/ANDROID_java_imports index 784d4843..43bdfb35 100644 --- a/modules/videoplayer/ANDROID_java_imports +++ b/modules/videoplayer/ANDROID_java_imports @@ -1,3 +1,5 @@ import java.io.File; +import android.support.v4.content.FileProvider; + diff --git a/modules/webview/ANDROID_application_attributes b/modules/webview/ANDROID_application_attributes new file mode 100644 index 00000000..a766aa5a --- /dev/null +++ b/modules/webview/ANDROID_application_attributes @@ -0,0 +1 @@ +android:usesCleartextTraffic="true" diff --git a/modules/webview/ANDROID_java_additions b/modules/webview/ANDROID_java_additions new file mode 100644 index 00000000..b3950fd2 --- /dev/null +++ b/modules/webview/ANDROID_java_additions @@ -0,0 +1,158 @@ +/* webview -*- mode: java; c-basic-offset: 2; -*- */ +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; } +} diff --git a/modules/webview/ANDROID_java_oncreate b/modules/webview/ANDROID_java_oncreate new file mode 100644 index 00000000..de2f2683 --- /dev/null +++ b/modules/webview/ANDROID_java_oncreate @@ -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)))) +*/ diff --git a/modules/webview/ANDROID_java_public_ProxySettings b/modules/webview/ANDROID_java_public_ProxySettings new file mode 100644 index 00000000..79e28945 --- /dev/null +++ b/modules/webview/ANDROID_java_public_ProxySettings @@ -0,0 +1,187 @@ +/* webview ProxySettings -*- mode: java; c-basic-offset: 2; -*- */ + +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 + ""); + + } +} diff --git a/modules/webview/MODULES b/modules/webview/MODULES new file mode 100644 index 00000000..9fbed2fa --- /dev/null +++ b/modules/webview/MODULES @@ -0,0 +1 @@ +ln_core lnjscheme diff --git a/modules/webview/webview.scm b/modules/webview/webview.scm new file mode 100644 index 00000000..e2302b34 --- /dev/null +++ b/modules/webview/webview.scm @@ -0,0 +1,159 @@ +(define (run-in-LNjScheme #!key (success values) (fail raise) #!rest body) + (thread-start! + (make-thread + (lambda () + (for-each + (lambda (expr) + (cond-expand + (android + ;; (log-debug "jScheme EVAL:" 1 expr) + (call-with-lnjscheme-result + expr + (lambda (promise) + (with-exception-catcher + (lambda (exn) + (log-error + (call-with-output-string + (lambda (port) + (display "EXN in\n" port) + (pretty-print expr port) + (display "EXN: " port) + (display-exception exn port))))) + (lambda () (success (force promise))))))) + (else #f))) + body)) + 'jscheme-worker)) + #t) + +(define (android-webview-code) + `(let* ((app ,(android-app-class)) + (this ln-this) + (intValue (method "intValue" "java.lang.Double")) ;; FIXME teach jscheme fixnums! + (String (lambda (x) (new "java.lang.String" x))) + ) + (let ( + (getWindow (method "getWindow" app)) + (getParent (method "getParent" "android.view.View")) ;; TBD: get rid of this + (setText (method "setText" "android.widget.TextView" "java.lang.CharSequence")) + (addView! (method "addView" "android.view.ViewGroup" "android.view.View")) + (addView/params! (method "addView" "android.view.ViewGroup" "android.view.View" + "android.view.ViewGroup$LayoutParams")) + (setContentView (method "setContentView" app "android.view.View")) + (addContentView (method "addContentView" app "android.view.View" "android.view.ViewGroup$LayoutParams")) + (setOrientation (method "setOrientation" "android.widget.LinearLayout" "int")) + ;; + (onclick-set! (method "LNjScheme_Set_OnClickListener" app "android.view.View" "java.lang.Object")) + (checkOrRequestPermission (method "checkOrRequestPermission" app "java.lang.String")) + (loadUrl (method "loadUrl" "android.webkit.WebView" "java.lang.String")) + (wv-can-go-back? (method "canGoBack" "android.webkit.WebView")) + (wv-goBack! (method "goBack" "android.webkit.WebView")) + (wv-setClient! (method "setWebViewClient" "android.webkit.WebView" "android.webkit.WebViewClient")) + ;; + (websettings (method "getSettings" "android.webkit.WebView")) + (wvs-javascript-enabled-set! (method "setJavaScriptEnabled" "android.webkit.WebSettings" "boolean")) + (wvs-zoom-support-set! (method "setSupportZoom" "android.webkit.WebSettings" "boolean")) + (wvs-zoom-builtin-set! (method "setBuiltInZoomControls" "android.webkit.WebSettings" "boolean")) + (wvs-zoom-builtin-controls-set! (method "setDisplayZoomControls" "android.webkit.WebSettings" "boolean")) + ) + (define (set-layout-vertical! x) + (setOrientation x (intValue 1))) + (define (arrange-in-order! parent childs) + (for-each (lambda (v) (addView! parent v)) childs)) + (let ( + (frame (new "android.widget.LinearLayout" this)) + (wv (make-webview this)) + (navigation (new "android.widget.LinearLayout" this)) ;; horizontal is default + (back (new "android.widget.Button" this)) + (back-pressed-h #f) + (reload (new "android.widget.Button" this)) + (Button3 (new "android.widget.Button" this)) + ) + (define (switch-back-to-glgui! v) + (on-back-pressed back-pressed-h) + (set! back-pressed-h #f) + (setContentView this ln-mglview)) + (define (back-pressed) + (if (wv-can-go-back? wv) (wv-goBack! wv) (switch-back-to-glgui! frame))) + ;; (webview! wv 'onpagecomplete (lambda (view url) (log-message "webview post visual state"))) + ;; (webview! wv 'onLoadResource (lambda (view url) (log-message (string-append "onLoadResource " url)))) + ;; (webview! wv 'onPageFinished (lambda (view url) (log-message (string-append "onPageFinished " url)))) + (let* ((wvs (websettings wv)) + (js+- (let ((is #f)) + (lambda _ + (set! is (not is)) + (wvs-javascript-enabled-set! wvs is))))) + ;; (wvs-javascript-enabled-set! wvs #t) + (begin + (setText Button3 (String "JS+-")) + (onclick-set! this Button3 js+-)) + (wvs-zoom-support-set! wvs #t) + (wvs-zoom-builtin-set! wvs #t) + (wvs-zoom-builtin-controls-set! wvs #f)) + (arrange-in-order! navigation (list back reload Button3)) + (setText back (String "Back")) + (setText reload (String "Reload")) + (onclick-set! this back switch-back-to-glgui!) + (onclick-set! this reload (lambda (v) ((method "reload" "android.webkit.WebView") wv))) + (set-layout-vertical! frame) + (set-layout-vertical! frame) + (arrange-in-order! frame (list navigation wv)) + (lambda (cmd arg) + (case cmd + ((load) (webview! wv cmd arg)) + (else + (if (not back-pressed-h) + (begin + (set! back-pressed-h (on-back-pressed)) + (on-back-pressed back-pressed))) + (setContentView this frame)))))))) + +(define android-webview + (let ((in-android-webview + (lambda (args) + (define (otherwise) + (log-error "android-webview: call not recognized" (object->string args)) + #f) + (cond + ((null? args) (otherwise)) + (else + (let ((a1 (car args))) + (cond + ((eq? a1 #t) '(webview #t #t)) + ((string? a1) `(webview 'load ,a1)) + (else (otherwise)))))))) + (webview-running #f)) + (lambda args + (cond + ((eq? webview-running #f) + (set! webview 'initializing) + (apply + run-in-LNjScheme + ;; success: (lambda _ (set! webview #t) (run-in-LNjScheme '(webview 'redraw #t))) + #;`(define (log-message str) + (let* ((app ,(android-app-class)) + ;; DOES NOT work on Android 10!!! + (log (method "ln_log" app "java.lang.String"))) + (log ln-this (new "java.lang.String" str)) + #t)) + ;; '(log-message "log-message working, app class:") + ;; `(log-message ,(debug 'android-app-class (android-app-class))) + `(if (not (bound? 'webview)) (define webview ,(android-webview-code))) + (map in-android-webview args)) + (set! webview-running #f)) + (else + (log-error + "android-webview: called again while previous call did not yet return. IGNORED: " + (object->string args)))) + #!void))) + +(define + webview-launch! + (let ((orginal-launch-url launch-url)) + (lambda (url #!key (via #f)) + (cond-expand + (android + (case via + ((webview) (android-webview '("about:blank") `(,url) '(#t))) + ((extern) (orginal-launch-url url)) + (else (orginal-launch-url url)))) + (else (orginal-launch-url url)))))) diff --git a/targets/android/build-binary b/targets/android/build-binary index 9c93e4a6..39c010af 100755 --- a/targets/android/build-binary +++ b/targets/android/build-binary @@ -144,8 +144,54 @@ else assert "### Gradle is not supported" fi +# try to include support.v4 library if [ -f $ANDROIDSDK/extras/android/support/v4/android-support-v4.jar ]; then cp -r $ANDROIDSDK/extras/android/support/v4/android-support-v4.jar $tmpdir/libs + echo " => support-v4 library loaded" +else + # look for support-v4 in alternate location + # special support for API 19 + if [ $SYS_ANDROIDAPI -eq "19" ]; then + if [ -f $ANDROIDSDK/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0.jar ]; then + cp -r $ANDROIDSDK/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0.jar $tmpdir/libs + echo " => support-v4 library loaded (version 19)" + fi + else + # Find the directory containing the support-v4 AAR file + # Only folders from 20 - 23 have the full AAR file needed + supportv4version=$SYS_ANDROIDAPI + if [ $supportv4version -gt 23 ]; then + supportv4version="23" + fi + v4dir="" + while [ $supportv4version -gt "19" ]; do + v4dir=`find $ANDROIDSDK/extras/android/m2repository/com/android/support/support-v4 -name "$supportv4version*" | grep -v -e "alpha" -e "beta" | sort | tail -1` + if [ ! "X$v4dir" = "X" ]; then + break + fi + supportv4version=`expr ${supportv4version} - 1` + done + # If a directory is found, check if classes.jar has already been extracted. If not, extract it from the AAR + if [ ! "X$v4dir" = "X" ]; then + v4file=`find $v4dir -name classes.jar` + if [ ! -s "$v4file" ]; then + v4aar=`find $v4dir -name support-v4*.aar` + if [ -s "$v4aar" ]; then + unzip $v4aar classes.jar -d $v4dir > /dev/null + v4file=`find $v4dir -name classes.jar` + fi + fi + # If a classes.jar file has been found, copy it to libs + if [ -s "$v4file" ]; then + cp -r $v4file $tmpdir/libs + echo " => support-v4 library loaded (version $supportv4version)" + else + echo " => warning: support-v4 library not found" + fi + else + echo " => warning: support-v4 library not found" + fi + fi fi if [ "$NEED_GCM" = "yes" ]; then @@ -185,7 +231,7 @@ if [ -d "$jarfilesdir" ]; then mkdir -p $tmpdir/libs/ for jar in $jarfiles; do locajar=`basename $jar | tr A-Z a-z` - vecho " => coping jar file - $locajar ..." + vecho " => copying jar file - $locajar ..." cp $jar $tmpdir/libs/ done fi @@ -197,7 +243,7 @@ if [ -d "$xmlfilesdir" ]; then mkdir -p $tmpdir/res/xml/ xmlfiles=`ls -1 $xmlfilesdir/*.xml 2> /dev/null` for xml in $xmlfiles; do - vecho " => coping xml file - $xml ..." + vecho " => copying xml file - $xml ..." cp $xml $tmpdir/res/xml/ done fi