github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/os_android.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package wm
     4  
     5  /*
     6  #cgo CFLAGS: -Werror
     7  #cgo LDFLAGS: -landroid
     8  
     9  #include <android/native_window_jni.h>
    10  #include <android/configuration.h>
    11  #include <android/keycodes.h>
    12  #include <android/input.h>
    13  #include <stdlib.h>
    14  
    15  static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
    16  	return (*vm)->GetEnv(vm, (void **)env, version);
    17  }
    18  
    19  static jint jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
    20  	return (*env)->GetJavaVM(env, jvm);
    21  }
    22  
    23  static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
    24  	return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
    25  }
    26  
    27  static jint jni_DetachCurrentThread(JavaVM *vm) {
    28  	return (*vm)->DetachCurrentThread(vm);
    29  }
    30  
    31  static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) {
    32  	return (*env)->NewGlobalRef(env, obj);
    33  }
    34  
    35  static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
    36  	(*env)->DeleteGlobalRef(env, obj);
    37  }
    38  
    39  static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) {
    40  	return (*env)->GetObjectClass(env, obj);
    41  }
    42  
    43  static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
    44  	return (*env)->GetMethodID(env, clazz, name, sig);
    45  }
    46  
    47  static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
    48  	return (*env)->GetStaticMethodID(env, clazz, name, sig);
    49  }
    50  
    51  static jfloat jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
    52  	return (*env)->CallFloatMethod(env, obj, methodID);
    53  }
    54  
    55  static jint jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
    56  	return (*env)->CallIntMethod(env, obj, methodID);
    57  }
    58  
    59  static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
    60  	(*env)->CallStaticVoidMethodA(env, cls, methodID, args);
    61  }
    62  
    63  static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
    64  	(*env)->CallVoidMethodA(env, obj, methodID, args);
    65  }
    66  
    67  static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
    68  	return (*env)->GetByteArrayElements(env, arr, NULL);
    69  }
    70  
    71  static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
    72  	(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
    73  }
    74  
    75  static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
    76  	return (*env)->GetArrayLength(env, arr);
    77  }
    78  
    79  static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
    80  	return (*env)->NewString(env, unicodeChars, len);
    81  }
    82  
    83  static jsize jni_GetStringLength(JNIEnv *env, jstring str) {
    84  	return (*env)->GetStringLength(env, str);
    85  }
    86  
    87  static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) {
    88  	return (*env)->GetStringChars(env, str, NULL);
    89  }
    90  
    91  static jthrowable jni_ExceptionOccurred(JNIEnv *env) {
    92  	return (*env)->ExceptionOccurred(env);
    93  }
    94  
    95  static void jni_ExceptionClear(JNIEnv *env) {
    96  	(*env)->ExceptionClear(env);
    97  }
    98  
    99  static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
   100  	return (*env)->CallObjectMethodA(env, obj, method, args);
   101  }
   102  
   103  static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
   104  	return (*env)->CallStaticObjectMethodA(env, cls, method, args);
   105  }
   106  */
   107  import "C"
   108  
   109  import (
   110  	"errors"
   111  	"fmt"
   112  	"image"
   113  	"image/color"
   114  	"reflect"
   115  	"runtime"
   116  	"runtime/debug"
   117  	"sync"
   118  	"time"
   119  	"unicode/utf16"
   120  	"unsafe"
   121  
   122  	"github.com/cybriq/giocore/internal/f32color"
   123  
   124  	"github.com/cybriq/giocore/f32"
   125  	"github.com/cybriq/giocore/io/clipboard"
   126  	"github.com/cybriq/giocore/io/key"
   127  	"github.com/cybriq/giocore/io/pointer"
   128  	"github.com/cybriq/giocore/io/system"
   129  	"github.com/cybriq/giocore/unit"
   130  )
   131  
   132  type window struct {
   133  	callbacks Callbacks
   134  
   135  	view C.jobject
   136  
   137  	dpi       int
   138  	fontScale float32
   139  	insets    system.Insets
   140  
   141  	stage     system.Stage
   142  	started   bool
   143  	animating bool
   144  
   145  	win *C.ANativeWindow
   146  }
   147  
   148  // gioView hold cached JNI methods for GioView.
   149  var gioView struct {
   150  	once               sync.Once
   151  	getDensity         C.jmethodID
   152  	getFontScale       C.jmethodID
   153  	showTextInput      C.jmethodID
   154  	hideTextInput      C.jmethodID
   155  	setInputHint       C.jmethodID
   156  	postFrameCallback  C.jmethodID
   157  	setCursor          C.jmethodID
   158  	setOrientation     C.jmethodID
   159  	setNavigationColor C.jmethodID
   160  	setStatusColor     C.jmethodID
   161  	setFullscreen      C.jmethodID
   162  }
   163  
   164  // ViewEvent is sent whenever the Window's underlying Android view
   165  // changes.
   166  type ViewEvent struct {
   167  	// View is a JNI global reference to the android.view.View
   168  	// instance backing the Window. The reference is valid until
   169  	// the next ViewEvent is received.
   170  	// A zero View means that there is currently no view attached.
   171  	View uintptr
   172  }
   173  
   174  type jvalue uint64 // The largest JNI type fits in 64 bits.
   175  
   176  var dataDirChan = make(chan string, 1)
   177  
   178  var android struct {
   179  	// mu protects all fields of this structure. However, once a
   180  	// non-nil jvm is returned from javaVM, all the other fields may
   181  	// be accessed unlocked.
   182  	mu  sync.Mutex
   183  	jvm *C.JavaVM
   184  
   185  	// appCtx is the global Android App context.
   186  	appCtx C.jobject
   187  	// gioCls is the class of the Gio class.
   188  	gioCls C.jclass
   189  
   190  	mwriteClipboard   C.jmethodID
   191  	mreadClipboard    C.jmethodID
   192  	mwakeupMainThread C.jmethodID
   193  }
   194  
   195  // view maps from GioView JNI refenreces to windows.
   196  var views = make(map[C.jlong]*window)
   197  
   198  // windows maps from Callbacks to windows
   199  var windows = make(map[Callbacks]*window)
   200  
   201  var mainWindow = newWindowRendezvous()
   202  
   203  var mainFuncs = make(chan func(env *C.JNIEnv), 1)
   204  
   205  func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
   206  	m := C.CString(method)
   207  	defer C.free(unsafe.Pointer(m))
   208  	s := C.CString(sig)
   209  	defer C.free(unsafe.Pointer(s))
   210  	jm := C.jni_GetMethodID(env, class, m, s)
   211  	if err := exception(env); err != nil {
   212  		panic(err)
   213  	}
   214  	return jm
   215  }
   216  
   217  func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
   218  	m := C.CString(method)
   219  	defer C.free(unsafe.Pointer(m))
   220  	s := C.CString(sig)
   221  	defer C.free(unsafe.Pointer(s))
   222  	jm := C.jni_GetStaticMethodID(env, class, m, s)
   223  	if err := exception(env); err != nil {
   224  		panic(err)
   225  	}
   226  	return jm
   227  }
   228  
   229  //export Java_org_gioui_Gio_runGoMain
   230  func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
   231  	initJVM(env, class, context)
   232  	dirBytes := C.jni_GetByteArrayElements(env, jdataDir)
   233  	if dirBytes == nil {
   234  		panic("runGoMain: GetByteArrayElements failed")
   235  	}
   236  	n := C.jni_GetArrayLength(env, jdataDir)
   237  	dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
   238  	dataDirChan <- dataDir
   239  	C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
   240  
   241  	runMain()
   242  }
   243  
   244  func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
   245  	android.mu.Lock()
   246  	defer android.mu.Unlock()
   247  	if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 {
   248  		panic("gio: GetJavaVM failed")
   249  	}
   250  	android.appCtx = C.jni_NewGlobalRef(env, ctx)
   251  	android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio)))
   252  	android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
   253  	android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
   254  	android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
   255  }
   256  
   257  func JavaVM() uintptr {
   258  	jvm := javaVM()
   259  	return uintptr(unsafe.Pointer(jvm))
   260  }
   261  
   262  func javaVM() *C.JavaVM {
   263  	android.mu.Lock()
   264  	defer android.mu.Unlock()
   265  	return android.jvm
   266  }
   267  
   268  func AppContext() uintptr {
   269  	android.mu.Lock()
   270  	defer android.mu.Unlock()
   271  	return uintptr(android.appCtx)
   272  }
   273  
   274  func GetDataDir() string {
   275  	return <-dataDirChan
   276  }
   277  
   278  //export Java_org_gioui_GioView_onCreateView
   279  func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
   280  	gioView.once.Do(func() {
   281  		m := &gioView
   282  		m.getDensity = getMethodID(env, class, "getDensity", "()I")
   283  		m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
   284  		m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
   285  		m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
   286  		m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
   287  		m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
   288  		m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
   289  		m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
   290  		m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V")
   291  		m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V")
   292  		m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V")
   293  	})
   294  	view = C.jni_NewGlobalRef(env, view)
   295  	wopts := <-mainWindow.out
   296  	w, ok := windows[wopts.window]
   297  	if !ok {
   298  		w = &window{
   299  			callbacks: wopts.window,
   300  		}
   301  		windows[wopts.window] = w
   302  	}
   303  	w.view = view
   304  	w.callbacks.SetDriver(w)
   305  	handle := C.jlong(view)
   306  	views[handle] = w
   307  	w.loadConfig(env, class)
   308  	w.Option(wopts.opts)
   309  	w.setStage(system.StagePaused)
   310  	w.callbacks.Event(ViewEvent{View: uintptr(view)})
   311  	return handle
   312  }
   313  
   314  //export Java_org_gioui_GioView_onDestroyView
   315  func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   316  	w := views[handle]
   317  	w.callbacks.Event(ViewEvent{View: 0})
   318  	w.callbacks.SetDriver(nil)
   319  	delete(views, handle)
   320  	C.jni_DeleteGlobalRef(env, w.view)
   321  	w.view = 0
   322  }
   323  
   324  //export Java_org_gioui_GioView_onStopView
   325  func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   326  	w := views[handle]
   327  	w.started = false
   328  	w.setStage(system.StagePaused)
   329  }
   330  
   331  //export Java_org_gioui_GioView_onStartView
   332  func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   333  	w := views[handle]
   334  	w.started = true
   335  	if w.win != nil {
   336  		w.setVisible()
   337  	}
   338  }
   339  
   340  //export Java_org_gioui_GioView_onSurfaceDestroyed
   341  func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   342  	w := views[handle]
   343  	w.win = nil
   344  	w.setStage(system.StagePaused)
   345  }
   346  
   347  //export Java_org_gioui_GioView_onSurfaceChanged
   348  func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
   349  	w := views[handle]
   350  	w.win = C.ANativeWindow_fromSurface(env, surf)
   351  	if w.started {
   352  		w.setVisible()
   353  	}
   354  }
   355  
   356  //export Java_org_gioui_GioView_onLowMemory
   357  func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
   358  	runtime.GC()
   359  	debug.FreeOSMemory()
   360  }
   361  
   362  //export Java_org_gioui_GioView_onConfigurationChanged
   363  func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
   364  	w := views[view]
   365  	w.loadConfig(env, class)
   366  	if w.stage >= system.StageRunning {
   367  		w.draw(true)
   368  	}
   369  }
   370  
   371  //export Java_org_gioui_GioView_onFrameCallback
   372  func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
   373  	w, exist := views[view]
   374  	if !exist {
   375  		return
   376  	}
   377  	if w.stage < system.StageRunning {
   378  		return
   379  	}
   380  	anim := w.animating
   381  	if anim {
   382  		runInJVM(javaVM(), func(env *C.JNIEnv) {
   383  			callVoidMethod(env, w.view, gioView.postFrameCallback)
   384  		})
   385  		w.draw(false)
   386  	}
   387  }
   388  
   389  //export Java_org_gioui_GioView_onBack
   390  func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
   391  	w := views[view]
   392  	ev := &system.CommandEvent{Type: system.CommandBack}
   393  	w.callbacks.Event(ev)
   394  	if ev.Cancel {
   395  		return C.JNI_TRUE
   396  	}
   397  	return C.JNI_FALSE
   398  }
   399  
   400  //export Java_org_gioui_GioView_onFocusChange
   401  func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
   402  	w := views[view]
   403  	go w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
   404  }
   405  
   406  //export Java_org_gioui_GioView_onWindowInsets
   407  func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
   408  	w := views[view]
   409  	w.insets = system.Insets{
   410  		Top:    unit.Px(float32(top)),
   411  		Right:  unit.Px(float32(right)),
   412  		Bottom: unit.Px(float32(bottom)),
   413  		Left:   unit.Px(float32(left)),
   414  	}
   415  	if w.stage >= system.StageRunning {
   416  		w.draw(true)
   417  	}
   418  }
   419  
   420  func (w *window) setVisible() {
   421  	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
   422  	if width == 0 || height == 0 {
   423  		return
   424  	}
   425  	w.setStage(system.StageRunning)
   426  	w.draw(true)
   427  }
   428  
   429  func (w *window) setStage(stage system.Stage) {
   430  	if stage == w.stage {
   431  		return
   432  	}
   433  	w.stage = stage
   434  	w.callbacks.Event(system.StageEvent{stage})
   435  }
   436  
   437  func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
   438  	var width, height int
   439  	if w.win != nil {
   440  		if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
   441  			panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
   442  		}
   443  		w, h := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
   444  		width, height = int(w), int(h)
   445  	}
   446  	return w.win, width, height
   447  }
   448  
   449  func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
   450  	dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity))
   451  	w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale))
   452  	switch dpi {
   453  	case C.ACONFIGURATION_DENSITY_NONE,
   454  		C.ACONFIGURATION_DENSITY_DEFAULT,
   455  		C.ACONFIGURATION_DENSITY_ANY:
   456  		// Assume standard density.
   457  		w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
   458  	default:
   459  		w.dpi = int(dpi)
   460  	}
   461  }
   462  
   463  func (w *window) SetAnimating(anim bool) {
   464  	w.animating = anim
   465  	if anim {
   466  		runInJVM(javaVM(), func(env *C.JNIEnv) {
   467  			callVoidMethod(env, w.view, gioView.postFrameCallback)
   468  		})
   469  	}
   470  }
   471  
   472  func (w *window) draw(sync bool) {
   473  	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
   474  	if width == 0 || height == 0 {
   475  		return
   476  	}
   477  	const inchPrDp = 1.0 / 160
   478  	ppdp := float32(w.dpi) * inchPrDp
   479  	w.callbacks.Event(FrameEvent{
   480  		FrameEvent: system.FrameEvent{
   481  			Now: time.Now(),
   482  			Size: image.Point{
   483  				X: int(width),
   484  				Y: int(height),
   485  			},
   486  			Insets: w.insets,
   487  			Metric: unit.Metric{
   488  				PxPerDp: ppdp,
   489  				PxPerSp: w.fontScale * ppdp,
   490  			},
   491  		},
   492  		Sync: sync,
   493  	})
   494  }
   495  
   496  type keyMapper func(devId, keyCode C.int32_t) rune
   497  
   498  func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
   499  	if jvm == nil {
   500  		panic("nil JVM")
   501  	}
   502  	runtime.LockOSThread()
   503  	defer runtime.UnlockOSThread()
   504  	var env *C.JNIEnv
   505  	if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
   506  		if res != C.JNI_EDETACHED {
   507  			panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
   508  		}
   509  		if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
   510  			panic(errors.New("runInJVM: AttachCurrentThread failed"))
   511  		}
   512  		defer C.jni_DetachCurrentThread(jvm)
   513  	}
   514  
   515  	f(env)
   516  }
   517  
   518  func convertKeyCode(code C.jint) (string, bool) {
   519  	var n string
   520  	switch code {
   521  	case C.AKEYCODE_DPAD_UP:
   522  		n = key.NameUpArrow
   523  	case C.AKEYCODE_DPAD_DOWN:
   524  		n = key.NameDownArrow
   525  	case C.AKEYCODE_DPAD_LEFT:
   526  		n = key.NameLeftArrow
   527  	case C.AKEYCODE_DPAD_RIGHT:
   528  		n = key.NameRightArrow
   529  	case C.AKEYCODE_FORWARD_DEL:
   530  		n = key.NameDeleteForward
   531  	case C.AKEYCODE_DEL:
   532  		n = key.NameDeleteBackward
   533  	case C.AKEYCODE_NUMPAD_ENTER:
   534  		n = key.NameEnter
   535  	case C.AKEYCODE_ENTER:
   536  		n = key.NameEnter
   537  	default:
   538  		return "", false
   539  	}
   540  	return n, true
   541  }
   542  
   543  //export Java_org_gioui_GioView_onKeyEvent
   544  func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
   545  	w := views[handle]
   546  	if n, ok := convertKeyCode(keyCode); ok {
   547  		w.callbacks.Event(key.Event{Name: n})
   548  	}
   549  	if r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
   550  		w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
   551  	}
   552  }
   553  
   554  //export Java_org_gioui_GioView_onTouchEvent
   555  func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
   556  	w := views[handle]
   557  	var typ pointer.Type
   558  	switch action {
   559  	case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
   560  		typ = pointer.Press
   561  	case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
   562  		typ = pointer.Release
   563  	case C.AMOTION_EVENT_ACTION_CANCEL:
   564  		typ = pointer.Cancel
   565  	case C.AMOTION_EVENT_ACTION_MOVE:
   566  		typ = pointer.Move
   567  	case C.AMOTION_EVENT_ACTION_SCROLL:
   568  		typ = pointer.Scroll
   569  	default:
   570  		return
   571  	}
   572  	var src pointer.Source
   573  	var btns pointer.Buttons
   574  	if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
   575  		btns |= pointer.ButtonPrimary
   576  	}
   577  	if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
   578  		btns |= pointer.ButtonSecondary
   579  	}
   580  	if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
   581  		btns |= pointer.ButtonTertiary
   582  	}
   583  	switch tool {
   584  	case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
   585  		src = pointer.Touch
   586  	case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
   587  		src = pointer.Mouse
   588  	case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
   589  		// For example, triggered via 'adb shell input tap'.
   590  		// Instead of discarding it, treat it as a touch event.
   591  		src = pointer.Touch
   592  	default:
   593  		return
   594  	}
   595  	w.callbacks.Event(pointer.Event{
   596  		Type:      typ,
   597  		Source:    src,
   598  		Buttons:   btns,
   599  		PointerID: pointer.ID(pointerID),
   600  		Time:      time.Duration(t) * time.Millisecond,
   601  		Position:  f32.Point{X: float32(x), Y: float32(y)},
   602  		Scroll:    f32.Pt(float32(scrollX), float32(scrollY)),
   603  	})
   604  }
   605  
   606  func (w *window) ShowTextInput(show bool) {
   607  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   608  		if show {
   609  			callVoidMethod(env, w.view, gioView.showTextInput)
   610  		} else {
   611  			callVoidMethod(env, w.view, gioView.hideTextInput)
   612  		}
   613  	})
   614  }
   615  
   616  func (w *window) SetInputHint(mode key.InputHint) {
   617  	// Constants defined at https://developer.android.com/reference/android/text/InputType.
   618  	const (
   619  		TYPE_NULL                = 0
   620  		TYPE_CLASS_NUMBER        = 2
   621  		TYPE_NUMBER_FLAG_DECIMAL = 8192
   622  		TYPE_NUMBER_FLAG_SIGNED  = 4096
   623  	)
   624  
   625  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   626  		var m jvalue
   627  		switch mode {
   628  		case key.HintNumeric:
   629  			m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
   630  		default:
   631  			// TYPE_NULL, since TYPE_CLASS_TEXT isn't currently supported (gio#116), so TYPE_NULL is used instead.
   632  			m = TYPE_NULL
   633  		}
   634  		callVoidMethod(env, w.view, gioView.setInputHint, m)
   635  	})
   636  }
   637  
   638  func javaString(env *C.JNIEnv, str string) C.jstring {
   639  	if str == "" {
   640  		return 0
   641  	}
   642  	utf16Chars := utf16.Encode([]rune(str))
   643  	return C.jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
   644  }
   645  
   646  func varArgs(args []jvalue) *C.jvalue {
   647  	if len(args) == 0 {
   648  		return nil
   649  	}
   650  	return (*C.jvalue)(unsafe.Pointer(&args[0]))
   651  }
   652  
   653  func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
   654  	C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
   655  	return exception(env)
   656  }
   657  
   658  func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
   659  	res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
   660  	return res, exception(env)
   661  }
   662  
   663  func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
   664  	C.jni_CallVoidMethodA(env, obj, method, varArgs(args))
   665  	return exception(env)
   666  }
   667  
   668  func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
   669  	res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args))
   670  	return res, exception(env)
   671  }
   672  
   673  // exception returns an error corresponding to the pending
   674  // exception, or nil if no exception is pending. The pending
   675  // exception is cleared.
   676  func exception(env *C.JNIEnv) error {
   677  	thr := C.jni_ExceptionOccurred(env)
   678  	if thr == 0 {
   679  		return nil
   680  	}
   681  	C.jni_ExceptionClear(env)
   682  	cls := getObjectClass(env, C.jobject(thr))
   683  	toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
   684  	msg, err := callObjectMethod(env, C.jobject(thr), toString)
   685  	if err != nil {
   686  		return err
   687  	}
   688  	return errors.New(goString(env, C.jstring(msg)))
   689  }
   690  
   691  func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
   692  	if obj == 0 {
   693  		panic("null object")
   694  	}
   695  	cls := C.jni_GetObjectClass(env, C.jobject(obj))
   696  	if err := exception(env); err != nil {
   697  		// GetObjectClass should never fail.
   698  		panic(err)
   699  	}
   700  	return cls
   701  }
   702  
   703  // goString converts the JVM jstring to a Go string.
   704  func goString(env *C.JNIEnv, str C.jstring) string {
   705  	if str == 0 {
   706  		return ""
   707  	}
   708  	strlen := C.jni_GetStringLength(env, C.jstring(str))
   709  	chars := C.jni_GetStringChars(env, C.jstring(str))
   710  	var utf16Chars []uint16
   711  	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
   712  	hdr.Data = uintptr(unsafe.Pointer(chars))
   713  	hdr.Cap = int(strlen)
   714  	hdr.Len = int(strlen)
   715  	utf8 := utf16.Decode(utf16Chars)
   716  	return string(utf8)
   717  }
   718  
   719  func Main() {
   720  }
   721  
   722  func NewWindow(window Callbacks, opts *Options) error {
   723  	mainWindow.in <- windowAndOptions{window, opts}
   724  	return <-mainWindow.errs
   725  }
   726  
   727  func (w *window) WriteClipboard(s string) {
   728  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   729  		jstr := javaString(env, s)
   730  		callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
   731  			jvalue(android.appCtx), jvalue(jstr))
   732  	})
   733  }
   734  
   735  func (w *window) ReadClipboard() {
   736  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   737  		c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
   738  			jvalue(android.appCtx))
   739  		if err != nil {
   740  			return
   741  		}
   742  		content := goString(env, C.jstring(c))
   743  		go w.callbacks.Event(clipboard.Event{Text: content})
   744  	})
   745  }
   746  
   747  func (w *window) Option(opts *Options) {
   748  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   749  		if o := opts.Orientation; o != nil {
   750  			setOrientation(env, w.view, *o)
   751  		}
   752  		if o := opts.NavigationColor; o != nil {
   753  			setNavigationColor(env, w.view, *o)
   754  		}
   755  		if o := opts.StatusColor; o != nil {
   756  			setStatusColor(env, w.view, *o)
   757  		}
   758  		if o := opts.WindowMode; o != nil {
   759  			setWindowMode(env, w.view, *o)
   760  		}
   761  	})
   762  }
   763  
   764  func (w *window) SetCursor(name pointer.CursorName) {
   765  	runInJVM(javaVM(), func(env *C.JNIEnv) {
   766  		setCursor(env, w.view, name)
   767  	})
   768  }
   769  
   770  func (w *window) Wakeup() {
   771  	runOnMain(func(env *C.JNIEnv) {
   772  		w.callbacks.Event(WakeupEvent{})
   773  	})
   774  }
   775  
   776  func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
   777  	var curID int
   778  	switch name {
   779  	default:
   780  		fallthrough
   781  	case pointer.CursorDefault:
   782  		curID = 1000 // TYPE_ARROW
   783  	case pointer.CursorText:
   784  		curID = 1008 // TYPE_TEXT
   785  	case pointer.CursorPointer:
   786  		curID = 1002 // TYPE_HAND
   787  	case pointer.CursorCrossHair:
   788  		curID = 1007 // TYPE_CROSSHAIR
   789  	case pointer.CursorColResize:
   790  		curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
   791  	case pointer.CursorRowResize:
   792  		curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
   793  	case pointer.CursorNone:
   794  		curID = 0 // TYPE_NULL
   795  	}
   796  	callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
   797  }
   798  
   799  func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
   800  	var (
   801  		id         int
   802  		idFallback int // Used only for SDK 17 or older.
   803  	)
   804  	// Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
   805  	switch mode {
   806  	case AnyOrientation:
   807  		id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
   808  	case LandscapeOrientation:
   809  		id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
   810  	case PortraitOrientation:
   811  		id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
   812  	}
   813  	callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
   814  }
   815  
   816  func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
   817  	callVoidMethod(env, view, gioView.setStatusColor,
   818  		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
   819  		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
   820  	)
   821  }
   822  
   823  func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
   824  	callVoidMethod(env, view, gioView.setNavigationColor,
   825  		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
   826  		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
   827  	)
   828  }
   829  
   830  func setWindowMode(env *C.JNIEnv, view C.jobject, mode WindowMode) {
   831  	switch mode {
   832  	case Fullscreen:
   833  		callVoidMethod(env, view, gioView.setFullscreen, C.JNI_TRUE)
   834  	default:
   835  		callVoidMethod(env, view, gioView.setFullscreen, C.JNI_FALSE)
   836  	}
   837  }
   838  
   839  // Close the window. Not implemented for Android.
   840  func (w *window) Close() {}
   841  
   842  // runOnMain runs a function on the Java main thread.
   843  func runOnMain(f func(env *C.JNIEnv)) {
   844  	go func() {
   845  		mainFuncs <- f
   846  		runInJVM(javaVM(), func(env *C.JNIEnv) {
   847  			callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
   848  		})
   849  	}()
   850  }
   851  
   852  //export Java_org_gioui_Gio_scheduleMainFuncs
   853  func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
   854  	for {
   855  		select {
   856  		case f := <-mainFuncs:
   857  			f(env)
   858  		default:
   859  			return
   860  		}
   861  	}
   862  }
   863  
   864  func (_ ViewEvent) ImplementsEvent() {}