From d6cfb903659c30d6db0b4caa670ee12f855e4739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Mon, 2 Nov 2020 11:26:22 +0100 Subject: [PATCH 1/2] ANDROID: additional error checking on JNI an more 1.) Android 9/10 restricts access to JNI and Jave reflection API's. This adds additional checks. Commented out an possible optimization, which could be assumed to works, but does not for me so far. Please leave this comment in, so we recall before we wonder why and try over and over again. Once in a while we should ponder if it still does not work or why. 2.) Clear Java exceptions. This enables to continue to run when an exception occured during a JNI call. (Future versions shall forward this as an exception in Gambit.) 3.) Avoid some JNI calls and provide more information about the Java/Android environment to Scheme. This is required for (upcoming) tricks to still call embedded dynamic libraries as subprocesses. It also enables to figure out a sane path to store app-private data instead of the deprecated hard coding of the publicly accessible path `/sdcard`. 4.) Add support to switch the apps content view to Java and back. An upcoming module `webview` will need this. --- loaders/android/bootstrap.c.in | 45 +++++++++++++++++---- loaders/android/bootstrap.java.in | 67 +++++++++++++++++++++---------- modules/config/config.scm | 14 +++++++ 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/loaders/android/bootstrap.c.in b/loaders/android/bootstrap.c.in index 267670d3..b7bdddc3 100644 --- a/loaders/android/bootstrap.c.in +++ b/loaders/android/bootstrap.c.in @@ -62,11 +62,23 @@ void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeEvent(JNIEnv* e, jobject // 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){ - globalObj = (*env)->NewGlobalRef(env,thiz); +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, NULL); + app_code_path = strdup((*env)->GetStringUTFChars(env, codePath, 0)); + (*env)->ReleaseStringUTFChars(env, codePath, NULL); } +char* android_getFilesDir() { return (char*) app_directory_files; } +char* android_getPackageCodePath() { return (char*) app_code_path; } + +char* android_getFilesDir_info_get() { return android_getFilesDir(); } + jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv *env; s_vm=vm; @@ -77,19 +89,38 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* GetJNIEnv(){ int error=0; JNIEnv* env = NULL; - if (s_vm) error=(*s_vm)->AttachCurrentThread(s_vm, &env, NULL); - if (!error&&(*env)->ExceptionCheck(env)) return NULL; + /* 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) error=(*s_vm)->AttachCurrentThread(s_vm, &env, NULL); + //if(!error&&(*env)->ExceptionCheck(env)) return NULL; + if(!error) error = JNI_forward_exception_to_gambit(env); return (error?NULL:env); } +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; +} + // 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); } } diff --git a/loaders/android/bootstrap.java.in b/loaders/android/bootstrap.java.in index dd99563d..d96ac4e1 100644 --- a/loaders/android/bootstrap.java.in +++ b/loaders/android/bootstrap.java.in @@ -126,20 +126,42 @@ 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) { + // Note: this is a bit brain deas as it ONLY handles GLSurfaceView + 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, @@ -147,7 +169,7 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ // prevent sleep getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mGLView = new xGLSurfaceView(this); - setContentView(mGLView); + // NOTE: we MAY better move the following lines BELOW nativeInstanceInit mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); checkOrRequestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE); @@ -155,13 +177,14 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ // Additions needed by modules, e.g. gps @ANDROID_JAVA_ONCREATE@ - // start EVENT_IDLE + nativeInstanceInit(getApplicationContext().getPackageCodePath().toString(), getFilesDir().toString()); + // start EVENT_IDLE + setContentView(mGLView); // MUST NOT run before nativeInstanceInit completed 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 +196,19 @@ 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) { @@ -220,13 +243,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 +264,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 +318,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 +334,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/config/config.scm b/modules/config/config.scm index 6880f1da..044232f1 100644 --- a/modules/config/config.scm +++ b/modules/config/config.scm @@ -94,4 +94,18 @@ end-of-c-declare (gambit-c (if (string=? (system-platform) "android") (##heartbeat-interval-set! -1.))) (else (if (string=? (system-platform) "android") (##set-heartbeat-interval! -1.)))) +(cond-expand + (android + (c-declare #< Date: Mon, 2 Nov 2020 12:13:31 +0100 Subject: [PATCH 2/2] ANDROID: actually use the new file information --- libraries/liblambdanative/system.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libraries/liblambdanative/system.c b/libraries/liblambdanative/system.c index db646096..4a932b74 100644 --- a/libraries/liblambdanative/system.c +++ b/libraries/liblambdanative/system.c @@ -150,10 +150,25 @@ 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]; +#if 0 sprintf(path,"/sdcard/%s", SYS_APPNAME); sys_appdir=strdup(path); sys_dir=strdup(path); +#endif +#if 0 + sprintf(path,"%s/system", android_getFilesDir()); + sys_dir=strdup(path); + sprintf(path,"%s/data", android_getFilesDir()); + sys_appdir=strdup(path); +#endif +#if 1 + sprintf(path,"/sdcard/%s", SYS_APPNAME); + sys_dir=strdup(path); + sys_appdir=android_getFilesDir(); +#endif + #endif #if defined(BB10) || defined(PLAYBOOK) char path[1024], cwd[1024];