~eliasnaur/gio-patches

app: add RegisterFragment method on *Window for Android v1 PROPOSED

Greg Pomerantz
Greg Pomerantz: 1
 app: add RegisterFragment method on *Window for Android

 8 files changed, 142 insertions(+), 15 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/9147/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] app: add RegisterFragment method on *Window for Android Export this patch

Greg Pomerantz
RegisterFragment creates an instance of a Java class and attempts
to register it as a Fragment in the window's Context.

Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
---
 app/app.go                        |  7 -----
 app/app_android.go                | 25 ++++++++++++++++
 app/internal/window/GioView.java  | 60 ++++++++++++++++++++++++++++++++++++++-
 app/internal/window/handle.go     |  7 -----
 app/internal/window/os_android.c  | 13 +++++++++
 app/internal/window/os_android.go | 19 +++++++++++++
 app/internal/window/os_android.h  |  3 ++
 app/window.go                     | 23 +++++++++++++++
 8 files changed, 142 insertions(+), 15 deletions(-)
 create mode 100644 app/app_android.go
 delete mode 100644 app/internal/window/handle.go

diff --git a/app/app.go b/app/app.go
index 9a18ec4..71bc57b 100644
--- a/app/app.go
+++ b/app/app.go
@@ -9,13 +9,6 @@ import (
	"gioui.org/app/internal/window"
)

type Handle window.Handle

// PlatformHandle returns the platform specific Handle.
func PlatformHandle() *Handle {
	return (*Handle)(window.PlatformHandle)
}

// extraArgs contains extra arguments to append to
// os.Args. The arguments are separated with |.
// Useful for running programs on mobiles where the
diff --git a/app/app_android.go b/app/app_android.go
new file mode 100644
index 0000000..a2ff2c6
--- /dev/null
+++ b/app/app_android.go
@@ -0,0 +1,25 @@
package app

import (
	"gioui.org/app/internal/window"
)

type Handle window.Handle

// PlatformHandle returns the Android platform-specific Handle.
func PlatformHandle() *Handle {
	return (*Handle)(window.PlatformHandle)
}

// RegisterFragment constructs a Java instance of the specified class
// and attempts to register it as a Fragment in the Context in which
// the View was created.
//
// NOTE: This method must not be called from the Gio application's main
// event loop, as that would block the View's UI thread and result in a
// deadlock.
func (w *Window) RegisterFragment(del string) error {
	errs := make(chan error)
	w.callbacks.Event(fragmentEvent{del, errs})
	return <- errs
}
diff --git a/app/internal/window/GioView.java b/app/internal/window/GioView.java
index 82bc36f..73f0aea 100644
--- a/app/internal/window/GioView.java
+++ b/app/internal/window/GioView.java
@@ -2,12 +2,24 @@

package org.gioui;

import java.lang.Class;
import java.lang.ClassLoader;
import java.lang.IllegalAccessException;
import java.lang.InstantiationException;
import java.lang.ExceptionInInitializerError;
import java.lang.SecurityException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.text.Editable;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -207,6 +219,52 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
		return onBack(nhandle);
	}

	public String registerFragment(String del) {
		final Class cls;
		try {
			cls = getContext().getClassLoader().loadClass(del);
		} catch (ClassNotFoundException e) {
			return ("RegisterFragment: fragment class not found: " + e.getMessage());
		}

		final BlockingQueue<String> q = new LinkedBlockingQueue<String>();

		handler.post(new Runnable() {
			public void run() {
				final Fragment frag;
				try {
					frag = (Fragment)cls.newInstance();
				} catch (IllegalAccessException | InstantiationException | ExceptionInInitializerError | SecurityException | ClassCastException e) {
					q.add("RegisterFragment: error instantiating fragment: " + e.getMessage());
					return;
				}
				final Context ctx = getContext();
				if (ctx == null) {
					q.add("RegisterFragment: GioView has null context");
					return;
				}
				final FragmentManager fm;
				try {
					fm = (FragmentManager)((Activity)(ctx)).getFragmentManager();
				} catch (ClassCastException e) {
					q.add("RegisterFragment: Cannot get Fragment manager from View Context: " + e.getMessage());
					return;
				}
				q.add("");
				FragmentTransaction ft = fm.beginTransaction();
				ft.add(frag, del);
				ft.commitNow();
			}
		});
		// NOTE: Because of the use of a BlockingQueue here, this method
		// cannot be called while the main UI thread is blocked.
		try {
			return q.take();
		} catch (InterruptedException e) {
			return ("RegisterFragment: interrupted: " + e.getMessage());
		}
	}

	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/handle.go b/app/internal/window/handle.go
deleted file mode 100644
index 5d7b1a1..0000000
--- a/app/internal/window/handle.go
@@ -1,7 +0,0 @@
// +build !android

package window

var PlatformHandle *Handle

type Handle struct{}
diff --git a/app/internal/window/os_android.c b/app/internal/window/os_android.c
index 5150ab6..435350d 100644
--- a/app/internal/window/os_android.c
+++ b/app/internal/window/os_android.c
@@ -166,3 +166,16 @@ void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes)
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
	return (*env)->GetArrayLength(env, arr);
}

jstring gio_jni_RegisterFragment(JNIEnv *env, jobject view, jmethodID mid, char* del) {
	jstring jdel = (*env)->NewStringUTF(env, del);
	return (*env)->CallObjectMethod(env, view, mid, jdel);
}

const char *gio_jni_GetStringUTFChars(JNIEnv *env, jstring string) {
	return (*env)->GetStringUTFChars(env, string, NULL);
}

void gio_jni_ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf) {
	(*env)->ReleaseStringUTFChars(env, string, utf);
}
diff --git a/app/internal/window/os_android.go b/app/internal/window/os_android.go
index 5cce88d..2d086f0 100644
--- a/app/internal/window/os_android.go
+++ b/app/internal/window/os_android.go
@@ -54,6 +54,7 @@ type window struct {
	mhideTextInput                 C.jmethodID
	mpostFrameCallback             C.jmethodID
	mpostFrameCallbackOnMainThread C.jmethodID
	mRegisterFragment              C.jmethodID
}

var dataDirChan = make(chan string, 1)
@@ -119,6 +120,7 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
		mhideTextInput:                 jniGetMethodID(env, class, "hideTextInput", "()V"),
		mpostFrameCallback:             jniGetMethodID(env, class, "postFrameCallback", "()V"),
		mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
		mRegisterFragment:              jniGetMethodID(env, class, "registerFragment", "(Ljava/lang/String;)Ljava/lang/String;"),
	}
	wopts := <-mainWindow.out
	w.callbacks = wopts.window
@@ -443,6 +445,23 @@ func (w *window) ShowTextInput(show bool) {
	})
}

func (w *window) RegisterFragment(del string, errs chan error) {
	ret := ""
	runInJVM(func(env *C.JNIEnv) {
		cdel := C.CString(del)
		defer C.free(unsafe.Pointer(cdel))
		jret := C.gio_jni_RegisterFragment(env, w.view, w.mRegisterFragment, cdel)
		utf := C.gio_jni_GetStringUTFChars(env, jret)
		ret = C.GoString(utf)
		C.gio_jni_ReleaseStringUTFChars(env, jret, utf)
	})
	if ret == "" {
		errs <- nil
	} else {
		errs <- errors.New(ret)
	}
}

func Main() {
}

diff --git a/app/internal/window/os_android.h b/app/internal/window/os_android.h
index 288d633..d17366c 100644
--- a/app/internal/window/os_android.h
+++ b/app/internal/window/os_android.h
@@ -17,3 +17,6 @@ __attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *en
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) jstring gio_jni_RegisterFragment(JNIEnv *env, jobject view, jmethodID mid, char* del);
__attribute__ ((visibility ("hidden"))) const char *gio_jni_GetStringUTFChars(JNIEnv *env, jstring string);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
diff --git a/app/window.go b/app/window.go
index 8b41d21..8aa1639 100644
--- a/app/window.go
+++ b/app/window.go
@@ -191,6 +191,19 @@ func (c *callbacks) Event(e event.Event) {
	<-c.w.ack
}

// fragmentEvent is sent on Android when the user wishes to register
// a Fragment with the window's View.
type fragmentEvent struct {
	name string
	errs chan error
}

// 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, chan error)
}

func (w *Window) waitAck() {
	// Send a dummy event; when it gets through we
	// know the application has processed the previous event.
@@ -316,6 +329,14 @@ func (w *Window) run(opts *window.Options) {
				w.waitAck()
			case driverEvent:
				w.driver = e2.driver
			case fragmentEvent:
				if w.driver == nil {
					w.ack <- struct{}{}
					e2.errs <- errors.New("RegisterFragment: no window driver found")
					continue
				}
				d := w.driver.(androidDriver)
				go d.RegisterFragment(e2.name, e2.errs)
			case system.DestroyEvent:
				w.destroyGPU()
				w.out <- e2
@@ -359,3 +380,5 @@ func Size(w, h unit.Value) Option {
}

func (driverEvent) ImplementsEvent() {}

func (fragmentEvent) ImplementsEvent() {}
-- 
2.16.2
Thank you for your patience. I hope that by spending the effort upfront, less
(total) time is required for maintaining the resulting API in the future.

On Mon Nov 25, 2019 at 10:08 AM Greg Pomerantz wrote: