diff --git a/app/app_android.go b/app/app_android.go index 5b648928..58e40d0d 100644 --- a/app/app_android.go +++ b/app/app_android.go @@ -17,18 +17,32 @@ func AppContext() uintptr { return window.AppContext() } -// androidDriver is an interface that allows the Window's run method -// to call the RegisterFragment method of the Android window driver. -type androidDriver interface { - RegisterFragment(string) -} - -// RegisterFragment constructs a Java instance of the specified class -// and registers it as a Fragment in the Context in which the View was -// created. -func (w *Window) RegisterFragment(del string) { - w.driverDo(func() { - d := w.driver.(androidDriver) - d.RegisterFragment(del) - }) +// Do invokes the function with a JNI jobject handle to the underlying +// Android View. The function is invoked on the main thread, and the +// handle is invalidated after the function returns. +// +// Note: Do may deadlock if called from the same goroutine that receives from +// Events. +func (w *Window) Do(f func(view uintptr)) { + type androidDriver interface { + Do(f func(view uintptr)) bool + } + success := make(chan bool) + for { + driver := make(chan androidDriver, 1) + // two-stage process: first wait for a valid driver... + w.driverDo(func() { + driver <- w.driver.(androidDriver) + }) + // .. then run the function on the main thread using the + // driver. The driver Do method returns false if the + // view was invalidated while switching to the main thread. + window.RunOnMain(func() { + d := <-driver + success <- d.Do(f) + }) + if <-success { + break + } + } } diff --git a/app/internal/window/GioView.java b/app/internal/window/GioView.java index b0885a5b..691c6dd8 100644 --- a/app/internal/window/GioView.java +++ b/app/internal/window/GioView.java @@ -191,31 +191,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal return onBack(nhandle); } - void registerFragment(String del) { - final Class cls; - try { - cls = getContext().getClassLoader().loadClass(del); - } catch (ClassNotFoundException e) { - throw new RuntimeException("RegisterFragment: fragment class not found: " + e.getMessage()); - } - - final Fragment frag; - try { - frag = (Fragment)cls.newInstance(); - } catch (IllegalAccessException | InstantiationException | ExceptionInInitializerError | SecurityException | ClassCastException e) { - throw new RuntimeException("RegisterFragment: error instantiating fragment: " + e.getMessage()); - } - final FragmentManager fm; - try { - fm = ((Activity)getContext()).getFragmentManager(); - } catch (ClassCastException e) { - throw new RuntimeException("RegisterFragment: cannot get fragment manager from View Context: " + e.getMessage()); - } - FragmentTransaction ft = fm.beginTransaction(); - ft.add(frag, del); - ft.commitNow(); - } - static private native long onCreateView(GioView view); static private native void onDestroyView(long handle); static private native void onStartView(long handle); diff --git a/app/internal/window/os_android.go b/app/internal/window/os_android.go index d73d5f0f..781f0550 100644 --- a/app/internal/window/os_android.go +++ b/app/internal/window/os_android.go @@ -80,7 +80,6 @@ type window struct { mshowTextInput C.jmethodID mhideTextInput C.jmethodID mpostFrameCallback C.jmethodID - mRegisterFragment C.jmethodID } type jvalue uint64 // The largest JNI type fits in 64 bits. @@ -193,7 +192,6 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j mshowTextInput: getMethodID(env, class, "showTextInput", "()V"), mhideTextInput: getMethodID(env, class, "hideTextInput", "()V"), mpostFrameCallback: getMethodID(env, class, "postFrameCallback", "()V"), - mRegisterFragment: getMethodID(env, class, "registerFragment", "(Ljava/lang/String;)V"), } wopts := <-mainWindow.out w.callbacks = wopts.window @@ -416,7 +414,6 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) { runtime.LockOSThread() defer runtime.UnlockOSThread() var env *C.JNIEnv - var detach bool if res := C.gio_jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { if res != C.JNI_EDETACHED { panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) @@ -424,14 +421,9 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) { if C.gio_jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK { panic(errors.New("runInJVM: AttachCurrentThread failed")) } - detach = true + defer C.gio_jni_DetachCurrentThread(jvm) } - if detach { - defer func() { - C.gio_jni_DetachCurrentThread(jvm) - }() - } f(env) } @@ -537,11 +529,21 @@ func javaString(env *C.JNIEnv, str string) C.jstring { return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars))) } -func (w *window) RegisterFragment(del string) { - w.runOnMain(func(env *C.JNIEnv) { - jstr := javaString(env, del) - callVoidMethod(env, w.view, w.mRegisterFragment, jvalue(jstr)) +// Do invokes the function with a global JNI handle to the view. If +// the view is destroyed, Do returns false and does not invoke the +// function. +// +// NOTE: Do must be invoked on the Android main thread. +func (w *window) Do(f func(view uintptr)) bool { + if w.view == 0 { + return false + } + runInJVM(javaVM(), func(env *C.JNIEnv) { + view := C.gio_jni_NewGlobalRef(env, w.view) + defer C.gio_jni_DeleteGlobalRef(env, view) + f(uintptr(view)) }) + return true } func varArgs(args []jvalue) *C.jvalue { @@ -645,6 +647,14 @@ func (w *window) ReadClipboard() { }) } +// RunOnMain is the exported version of runOnMain without a JNI +// environement. +func RunOnMain(f func()) { + runOnMain(func(_ *C.JNIEnv) { + f() + }) +} + // runOnMain runs a function on the Java main thread. func runOnMain(f func(env *C.JNIEnv)) { go func() { diff --git a/app/permission/doc.go b/app/permission/doc.go index 4aa59efb..ec8eb703 100644 --- a/app/permission/doc.go +++ b/app/permission/doc.go @@ -37,10 +37,14 @@ program's source code: Android -- Dangerous Permissions Certain permissions on Android are marked with a protection level of -"dangerous". This means that, in addition to including the relevant Gio -permission packages, your app will need to prompt the user specifically -to request access. This can be done with a java Fragment, installed using -(*app.Window).RegisterFragment(). For more information on dangerous -permissions, see: https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions +"dangerous". This means that, in addition to including the relevant +Gio permission packages, your app will need to prompt the user +specifically to request access. To access the Android Activity +required for prompting, use the Do method on Window. Do exposes +the underlying Android View, on which the getContext method returns +the Activity. + +For more information on dangerous permissions, see: +https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions */ package permission