github.com/utopiagio/gio@v0.0.8/app/os_android.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     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 jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
    68  	return (*env)->CallBooleanMethodA(env, obj, methodID, args);
    69  }
    70  
    71  static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
    72  	return (*env)->GetByteArrayElements(env, arr, NULL);
    73  }
    74  
    75  static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
    76  	(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
    77  }
    78  
    79  static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
    80  	return (*env)->GetArrayLength(env, arr);
    81  }
    82  
    83  static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
    84  	return (*env)->NewString(env, unicodeChars, len);
    85  }
    86  
    87  static jsize jni_GetStringLength(JNIEnv *env, jstring str) {
    88  	return (*env)->GetStringLength(env, str);
    89  }
    90  
    91  static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) {
    92  	return (*env)->GetStringChars(env, str, NULL);
    93  }
    94  
    95  static jthrowable jni_ExceptionOccurred(JNIEnv *env) {
    96  	return (*env)->ExceptionOccurred(env);
    97  }
    98  
    99  static void jni_ExceptionClear(JNIEnv *env) {
   100  	(*env)->ExceptionClear(env);
   101  }
   102  
   103  static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
   104  	return (*env)->CallObjectMethodA(env, obj, method, args);
   105  }
   106  
   107  static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
   108  	return (*env)->CallStaticObjectMethodA(env, cls, method, args);
   109  }
   110  
   111  static jclass jni_FindClass(JNIEnv *env, char *name) {
   112  	return (*env)->FindClass(env, name);
   113  }
   114  
   115  static jobject jni_NewObjectA(JNIEnv *env, jclass cls, jmethodID cons, jvalue *args) {
   116  	return (*env)->NewObjectA(env, cls, cons, args);
   117  }
   118  */
   119  import "C"
   120  
   121  import (
   122  	"errors"
   123  	"fmt"
   124  	"image"
   125  	"image/color"
   126  	"io"
   127  	"math"
   128  	"os"
   129  	"path/filepath"
   130  	"runtime"
   131  	"runtime/cgo"
   132  	"runtime/debug"
   133  	"strings"
   134  	"sync"
   135  	"time"
   136  	"unicode/utf16"
   137  	"unsafe"
   138  
   139  	"github.com/utopiagio/gio/internal/f32color"
   140  	
   141  	"github.com/utopiagio/gio/op"
   142  
   143  	"github.com/utopiagio/gio/f32"
   144  	"github.com/utopiagio/gio/io/event"
   145  	"github.com/utopiagio/gio/io/input"
   146  	"github.com/utopiagio/gio/io/key"
   147  	"github.com/utopiagio/gio/io/pointer"
   148  	"github.com/utopiagio/gio/io/semantic"
   149  	"github.com/utopiagio/gio/io/system"
   150  	"github.com/utopiagio/gio/io/transfer"
   151  	"github.com/utopiagio/gio/unit"
   152  )
   153  
   154  type window struct {
   155  	callbacks *callbacks
   156  	loop      *eventLoop
   157  
   158  	view   C.jobject
   159  	handle cgo.Handle
   160  
   161  	dpi       int
   162  	fontScale float32
   163  	insets    pixelInsets
   164  
   165  	visible   bool
   166  	started   bool
   167  	animating bool
   168  
   169  	win       *C.ANativeWindow
   170  	config    Config
   171  	inputHint key.InputHint
   172  
   173  	semantic struct {
   174  		hoverID input.SemanticID
   175  		rootID  input.SemanticID
   176  		focusID input.SemanticID
   177  		diffs   []input.SemanticID
   178  	}
   179  }
   180  
   181  // gioView hold cached JNI methods for GioView.
   182  var gioView struct {
   183  	once               sync.Once
   184  	getDensity         C.jmethodID
   185  	getFontScale       C.jmethodID
   186  	showTextInput      C.jmethodID
   187  	hideTextInput      C.jmethodID
   188  	setInputHint       C.jmethodID
   189  	postFrameCallback  C.jmethodID
   190  	invalidate         C.jmethodID // requests draw, called from UI thread
   191  	setCursor          C.jmethodID
   192  	setOrientation     C.jmethodID
   193  	setNavigationColor C.jmethodID
   194  	setStatusColor     C.jmethodID
   195  	setFullscreen      C.jmethodID
   196  	unregister         C.jmethodID
   197  	sendA11yEvent      C.jmethodID
   198  	sendA11yChange     C.jmethodID
   199  	isA11yActive       C.jmethodID
   200  	restartInput       C.jmethodID
   201  	updateSelection    C.jmethodID
   202  	updateCaret        C.jmethodID
   203  }
   204  
   205  type pixelInsets struct {
   206  	top, bottom, left, right int
   207  }
   208  
   209  // AndroidViewEvent is sent whenever the Window's underlying Android view
   210  // changes.
   211  type AndroidViewEvent struct {
   212  	// View is a JNI global reference to the android.view.View
   213  	// instance backing the Window. The reference is valid until
   214  	// the next ViewEvent is received.
   215  	// A zero View means that there is currently no view attached.
   216  	View uintptr
   217  }
   218  
   219  type jvalue uint64 // The largest JNI type fits in 64 bits.
   220  
   221  var dataDirChan = make(chan string, 1)
   222  
   223  var android struct {
   224  	// mu protects all fields of this structure. However, once a
   225  	// non-nil jvm is returned from javaVM, all the other fields may
   226  	// be accessed unlocked.
   227  	mu  sync.Mutex
   228  	jvm *C.JavaVM
   229  
   230  	// appCtx is the global Android App context.
   231  	appCtx C.jobject
   232  	// gioCls is the class of the Gio class.
   233  	gioCls C.jclass
   234  
   235  	mwriteClipboard   C.jmethodID
   236  	mreadClipboard    C.jmethodID
   237  	mwakeupMainThread C.jmethodID
   238  
   239  	// android.view.accessibility.AccessibilityNodeInfo class.
   240  	accessibilityNodeInfo struct {
   241  		cls C.jclass
   242  		// addChild(View, int)
   243  		addChild C.jmethodID
   244  		// setBoundsInScreen(Rect)
   245  		setBoundsInScreen C.jmethodID
   246  		// setText(CharSequence)
   247  		setText C.jmethodID
   248  		// setContentDescription(CharSequence)
   249  		setContentDescription C.jmethodID
   250  		// setParent(View, int)
   251  		setParent C.jmethodID
   252  		// addAction(int)
   253  		addAction C.jmethodID
   254  		// setClassName(CharSequence)
   255  		setClassName C.jmethodID
   256  		// setCheckable(boolean)
   257  		setCheckable C.jmethodID
   258  		// setSelected(boolean)
   259  		setSelected C.jmethodID
   260  		// setChecked(boolean)
   261  		setChecked C.jmethodID
   262  		// setEnabled(boolean)
   263  		setEnabled C.jmethodID
   264  		// setAccessibilityFocused(boolean)
   265  		setAccessibilityFocused C.jmethodID
   266  	}
   267  
   268  	// android.graphics.Rect class.
   269  	rect struct {
   270  		cls C.jclass
   271  		// (int, int, int, int) constructor.
   272  		cons C.jmethodID
   273  	}
   274  
   275  	strings struct {
   276  		// "android.view.View"
   277  		androidViewView C.jstring
   278  		// "android.widget.Button"
   279  		androidWidgetButton C.jstring
   280  		// "android.widget.CheckBox"
   281  		androidWidgetCheckBox C.jstring
   282  		// "android.widget.EditText"
   283  		androidWidgetEditText C.jstring
   284  		// "android.widget.RadioButton"
   285  		androidWidgetRadioButton C.jstring
   286  		// "android.widget.Switch"
   287  		androidWidgetSwitch C.jstring
   288  	}
   289  }
   290  
   291  var windows = make(map[*callbacks]*window)
   292  
   293  var mainWindow = newWindowRendezvous()
   294  
   295  var mainFuncs = make(chan func(env *C.JNIEnv), 1)
   296  
   297  var (
   298  	dataDirOnce sync.Once
   299  	dataPath    string
   300  )
   301  
   302  var (
   303  	newAndroidVulkanContext func(w *window) (context, error)
   304  	newAndroidGLESContext   func(w *window) (context, error)
   305  )
   306  
   307  // AccessibilityNodeProvider.HOST_VIEW_ID.
   308  const HOST_VIEW_ID = -1
   309  
   310  const (
   311  	// AccessibilityEvent constants.
   312  	TYPE_VIEW_HOVER_ENTER = 128
   313  	TYPE_VIEW_HOVER_EXIT  = 256
   314  )
   315  
   316  const (
   317  	// AccessibilityNodeInfo constants.
   318  	ACTION_ACCESSIBILITY_FOCUS       = 64
   319  	ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128
   320  	ACTION_CLICK                     = 16
   321  )
   322  
   323  func (w *window) NewContext() (context, error) {
   324  	funcs := []func(w *window) (context, error){newAndroidGLESContext, newAndroidVulkanContext}
   325  	var firstErr error
   326  	for _, f := range funcs {
   327  		if f == nil {
   328  			continue
   329  		}
   330  		c, err := f(w)
   331  		if err != nil {
   332  			if firstErr == nil {
   333  				firstErr = err
   334  			}
   335  			continue
   336  		}
   337  		return c, nil
   338  	}
   339  	if firstErr != nil {
   340  		return nil, firstErr
   341  	}
   342  	return nil, errors.New("x11: no available GPU backends")
   343  }
   344  
   345  func dataDir() (string, error) {
   346  	dataDirOnce.Do(func() {
   347  		dataPath = <-dataDirChan
   348  	})
   349  	return dataPath, nil
   350  }
   351  
   352  func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
   353  	m := C.CString(method)
   354  	defer C.free(unsafe.Pointer(m))
   355  	s := C.CString(sig)
   356  	defer C.free(unsafe.Pointer(s))
   357  	jm := C.jni_GetMethodID(env, class, m, s)
   358  	if err := exception(env); err != nil {
   359  		panic(err)
   360  	}
   361  	return jm
   362  }
   363  
   364  func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
   365  	m := C.CString(method)
   366  	defer C.free(unsafe.Pointer(m))
   367  	s := C.CString(sig)
   368  	defer C.free(unsafe.Pointer(s))
   369  	jm := C.jni_GetStaticMethodID(env, class, m, s)
   370  	if err := exception(env); err != nil {
   371  		panic(err)
   372  	}
   373  	return jm
   374  }
   375  
   376  //export Java_org_gioui_Gio_runGoMain
   377  func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
   378  	initJVM(env, class, context)
   379  	dirBytes := C.jni_GetByteArrayElements(env, jdataDir)
   380  	if dirBytes == nil {
   381  		panic("runGoMain: GetByteArrayElements failed")
   382  	}
   383  	n := C.jni_GetArrayLength(env, jdataDir)
   384  	dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
   385  
   386  	// Set XDG_CACHE_HOME to make os.UserCacheDir work.
   387  	if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
   388  		cachePath := filepath.Join(dataDir, "cache")
   389  		os.Setenv("XDG_CACHE_HOME", cachePath)
   390  	}
   391  	// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
   392  	if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
   393  		cfgPath := filepath.Join(dataDir, "config")
   394  		os.Setenv("XDG_CONFIG_HOME", cfgPath)
   395  	}
   396  	// Set HOME to make os.UserHomeDir work.
   397  	if _, exists := os.LookupEnv("HOME"); !exists {
   398  		os.Setenv("HOME", dataDir)
   399  	}
   400  
   401  	dataDirChan <- dataDir
   402  	C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
   403  
   404  	runMain()
   405  }
   406  
   407  func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
   408  	android.mu.Lock()
   409  	defer android.mu.Unlock()
   410  	if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 {
   411  		panic("gio: GetJavaVM failed")
   412  	}
   413  	android.appCtx = C.jni_NewGlobalRef(env, ctx)
   414  	android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio)))
   415  
   416  	cls := findClass(env, "android/view/accessibility/AccessibilityNodeInfo")
   417  	android.accessibilityNodeInfo.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
   418  	android.accessibilityNodeInfo.addChild = getMethodID(env, cls, "addChild", "(Landroid/view/View;I)V")
   419  	android.accessibilityNodeInfo.setBoundsInScreen = getMethodID(env, cls, "setBoundsInScreen", "(Landroid/graphics/Rect;)V")
   420  	android.accessibilityNodeInfo.setText = getMethodID(env, cls, "setText", "(Ljava/lang/CharSequence;)V")
   421  	android.accessibilityNodeInfo.setContentDescription = getMethodID(env, cls, "setContentDescription", "(Ljava/lang/CharSequence;)V")
   422  	android.accessibilityNodeInfo.setParent = getMethodID(env, cls, "setParent", "(Landroid/view/View;I)V")
   423  	android.accessibilityNodeInfo.addAction = getMethodID(env, cls, "addAction", "(I)V")
   424  	android.accessibilityNodeInfo.setClassName = getMethodID(env, cls, "setClassName", "(Ljava/lang/CharSequence;)V")
   425  	android.accessibilityNodeInfo.setCheckable = getMethodID(env, cls, "setCheckable", "(Z)V")
   426  	android.accessibilityNodeInfo.setSelected = getMethodID(env, cls, "setSelected", "(Z)V")
   427  	android.accessibilityNodeInfo.setChecked = getMethodID(env, cls, "setChecked", "(Z)V")
   428  	android.accessibilityNodeInfo.setEnabled = getMethodID(env, cls, "setEnabled", "(Z)V")
   429  	android.accessibilityNodeInfo.setAccessibilityFocused = getMethodID(env, cls, "setAccessibilityFocused", "(Z)V")
   430  
   431  	cls = findClass(env, "android/graphics/Rect")
   432  	android.rect.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
   433  	android.rect.cons = getMethodID(env, cls, "<init>", "(IIII)V")
   434  	android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
   435  	android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
   436  	android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
   437  
   438  	intern := func(s string) C.jstring {
   439  		ref := C.jni_NewGlobalRef(env, C.jobject(javaString(env, s)))
   440  		return C.jstring(ref)
   441  	}
   442  	android.strings.androidViewView = intern("android.view.View")
   443  	android.strings.androidWidgetButton = intern("android.widget.Button")
   444  	android.strings.androidWidgetCheckBox = intern("android.widget.CheckBox")
   445  	android.strings.androidWidgetEditText = intern("android.widget.EditText")
   446  	android.strings.androidWidgetRadioButton = intern("android.widget.RadioButton")
   447  	android.strings.androidWidgetSwitch = intern("android.widget.Switch")
   448  }
   449  
   450  // JavaVM returns the global JNI JavaVM.
   451  func JavaVM() uintptr {
   452  	jvm := javaVM()
   453  	return uintptr(unsafe.Pointer(jvm))
   454  }
   455  
   456  func javaVM() *C.JavaVM {
   457  	android.mu.Lock()
   458  	defer android.mu.Unlock()
   459  	return android.jvm
   460  }
   461  
   462  // AppContext returns the global Application context as a JNI jobject.
   463  func AppContext() uintptr {
   464  	android.mu.Lock()
   465  	defer android.mu.Unlock()
   466  	return uintptr(android.appCtx)
   467  }
   468  
   469  //export Java_org_gioui_GioView_onCreateView
   470  func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
   471  	gioView.once.Do(func() {
   472  		m := &gioView
   473  		m.getDensity = getMethodID(env, class, "getDensity", "()I")
   474  		m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
   475  		m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
   476  		m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
   477  		m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
   478  		m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
   479  		m.invalidate = getMethodID(env, class, "invalidate", "()V")
   480  		m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
   481  		m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
   482  		m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V")
   483  		m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V")
   484  		m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V")
   485  		m.unregister = getMethodID(env, class, "unregister", "()V")
   486  		m.sendA11yEvent = getMethodID(env, class, "sendA11yEvent", "(II)V")
   487  		m.sendA11yChange = getMethodID(env, class, "sendA11yChange", "(I)V")
   488  		m.isA11yActive = getMethodID(env, class, "isA11yActive", "()Z")
   489  		m.restartInput = getMethodID(env, class, "restartInput", "()V")
   490  		m.updateSelection = getMethodID(env, class, "updateSelection", "()V")
   491  		m.updateCaret = getMethodID(env, class, "updateCaret", "(FFFFFFFFFF)V")
   492  	})
   493  	view = C.jni_NewGlobalRef(env, view)
   494  	wopts := <-mainWindow.out
   495  	var cnf Config
   496  	w, ok := windows[wopts.window]
   497  	if !ok {
   498  		w = &window{
   499  			callbacks: wopts.window,
   500  		}
   501  		w.loop = newEventLoop(w.callbacks, w.wakeup)
   502  		w.callbacks.SetDriver(w)
   503  		cnf.apply(unit.Metric{}, wopts.options)
   504  		windows[wopts.window] = w
   505  	} else {
   506  		cnf = w.config
   507  	}
   508  	mainWindow.windows <- struct{}{}
   509  	if w.view != 0 {
   510  		w.detach(env)
   511  	}
   512  	w.view = view
   513  	w.visible = false
   514  	w.handle = cgo.NewHandle(w)
   515  	w.loadConfig(env, class)
   516  	w.setConfig(env, cnf)
   517  	w.SetInputHint(w.inputHint)
   518  	w.processEvent(AndroidViewEvent{View: uintptr(view)})
   519  	return C.jlong(w.handle)
   520  }
   521  
   522  //export Java_org_gioui_GioView_onDestroyView
   523  func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   524  	w := cgo.Handle(handle).Value().(*window)
   525  	w.detach(env)
   526  }
   527  
   528  //export Java_org_gioui_GioView_onStopView
   529  func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   530  	w := cgo.Handle(handle).Value().(*window)
   531  	w.started = false
   532  	w.visible = false
   533  }
   534  
   535  //export Java_org_gioui_GioView_onStartView
   536  func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   537  	w := cgo.Handle(handle).Value().(*window)
   538  	w.started = true
   539  	if w.win != nil {
   540  		w.setVisible(env)
   541  	}
   542  }
   543  
   544  //export Java_org_gioui_GioView_onSurfaceDestroyed
   545  func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   546  	w := cgo.Handle(handle).Value().(*window)
   547  	w.win = nil
   548  	w.visible = false
   549  }
   550  
   551  //export Java_org_gioui_GioView_onSurfaceChanged
   552  func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
   553  	w := cgo.Handle(handle).Value().(*window)
   554  	w.win = C.ANativeWindow_fromSurface(env, surf)
   555  	if w.started {
   556  		w.setVisible(env)
   557  	}
   558  }
   559  
   560  //export Java_org_gioui_GioView_onLowMemory
   561  func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
   562  	runtime.GC()
   563  	debug.FreeOSMemory()
   564  }
   565  
   566  //export Java_org_gioui_GioView_onConfigurationChanged
   567  func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
   568  	w := cgo.Handle(view).Value().(*window)
   569  	w.loadConfig(env, class)
   570  	w.draw(env, true)
   571  }
   572  
   573  //export Java_org_gioui_GioView_onFrameCallback
   574  func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong) {
   575  	w, exist := cgo.Handle(view).Value().(*window)
   576  	if !exist {
   577  		return
   578  	}
   579  	if w.visible && w.animating {
   580  		w.draw(env, false)
   581  		callVoidMethod(env, w.view, gioView.postFrameCallback)
   582  	}
   583  }
   584  
   585  //export Java_org_gioui_GioView_onBack
   586  func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
   587  	w := cgo.Handle(view).Value().(*window)
   588  	if w.processEvent(key.Event{Name: key.NameBack}) {
   589  		return C.JNI_TRUE
   590  	}
   591  	return C.JNI_FALSE
   592  }
   593  
   594  //export Java_org_gioui_GioView_onFocusChange
   595  func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
   596  	w := cgo.Handle(view).Value().(*window)
   597  	w.config.Focused = focus == C.JNI_TRUE
   598  	w.processEvent(ConfigEvent{Config: w.config})
   599  }
   600  
   601  //export Java_org_gioui_GioView_onWindowInsets
   602  func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
   603  	w := cgo.Handle(view).Value().(*window)
   604  	w.insets = pixelInsets{
   605  		top:    int(top),
   606  		bottom: int(bottom),
   607  		left:   int(left),
   608  		right:  int(right),
   609  	}
   610  	w.draw(env, true)
   611  }
   612  
   613  //export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
   614  func Java_org_gioui_GioView_initializeAccessibilityNodeInfo(env *C.JNIEnv, class C.jclass, view C.jlong, virtID, screenX, screenY C.jint, info C.jobject) C.jobject {
   615  	w := cgo.Handle(view).Value().(*window)
   616  	semID := w.semIDFor(virtID)
   617  	sem, found := w.callbacks.LookupSemantic(semID)
   618  	if found {
   619  		off := image.Pt(int(screenX), int(screenY))
   620  		if err := w.initAccessibilityNodeInfo(env, sem, off, info); err != nil {
   621  			panic(err)
   622  		}
   623  	}
   624  	return info
   625  }
   626  
   627  //export Java_org_gioui_GioView_onTouchExploration
   628  func Java_org_gioui_GioView_onTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong, x, y C.jfloat) {
   629  	w := cgo.Handle(view).Value().(*window)
   630  	semID, _ := w.callbacks.SemanticAt(f32.Pt(float32(x), float32(y)))
   631  	if w.semantic.hoverID == semID {
   632  		return
   633  	}
   634  	// Android expects ENTER before EXIT.
   635  	if semID != 0 {
   636  		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_ENTER, jvalue(w.virtualIDFor(semID)))
   637  	}
   638  	if prevID := w.semantic.hoverID; prevID != 0 {
   639  		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(prevID)))
   640  	}
   641  	w.semantic.hoverID = semID
   642  }
   643  
   644  //export Java_org_gioui_GioView_onExitTouchExploration
   645  func Java_org_gioui_GioView_onExitTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong) {
   646  	w := cgo.Handle(view).Value().(*window)
   647  	if w.semantic.hoverID != 0 {
   648  		callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(w.semantic.hoverID)))
   649  		w.semantic.hoverID = 0
   650  	}
   651  }
   652  
   653  //export Java_org_gioui_GioView_onA11yFocus
   654  func Java_org_gioui_GioView_onA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
   655  	w := cgo.Handle(view).Value().(*window)
   656  	if semID := w.semIDFor(virtID); semID != w.semantic.focusID {
   657  		w.semantic.focusID = semID
   658  		// Android needs invalidate to refresh the TalkBack focus indicator.
   659  		callVoidMethod(env, w.view, gioView.invalidate)
   660  	}
   661  }
   662  
   663  //export Java_org_gioui_GioView_onClearA11yFocus
   664  func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
   665  	w := cgo.Handle(view).Value().(*window)
   666  	if w.semantic.focusID == w.semIDFor(virtID) {
   667  		w.semantic.focusID = 0
   668  	}
   669  }
   670  
   671  func (w *window) ProcessEvent(e event.Event) {
   672  	w.processEvent(e)
   673  }
   674  
   675  func (w *window) processEvent(e event.Event) bool {
   676  	if !w.callbacks.ProcessEvent(e) {
   677  		return false
   678  	}
   679  	w.loop.FlushEvents()
   680  	return true
   681  }
   682  
   683  func (w *window) Event() event.Event {
   684  	return w.loop.Event()
   685  }
   686  
   687  func (w *window) Invalidate() {
   688  	w.loop.Invalidate()
   689  }
   690  
   691  func (w *window) Run(f func()) {
   692  	w.loop.Run(f)
   693  }
   694  
   695  func (w *window) Frame(frame *op.Ops) {
   696  	w.loop.Frame(frame)
   697  }
   698  
   699  func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
   700  	for _, ch := range sem.Children {
   701  		err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
   702  		if err != nil {
   703  			return err
   704  		}
   705  	}
   706  	if sem.ParentID != 0 {
   707  		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setParent, jvalue(w.view), jvalue(w.virtualIDFor(sem.ParentID))); err != nil {
   708  			return err
   709  		}
   710  		b := sem.Desc.Bounds.Add(off)
   711  		rect, err := newObject(env, android.rect.cls, android.rect.cons,
   712  			jvalue(b.Min.X),
   713  			jvalue(b.Min.Y),
   714  			jvalue(b.Max.X),
   715  			jvalue(b.Max.Y),
   716  		)
   717  		if err != nil {
   718  			return err
   719  		}
   720  		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setBoundsInScreen, jvalue(rect)); err != nil {
   721  			return err
   722  		}
   723  	}
   724  	d := sem.Desc
   725  	if l := d.Label; l != "" {
   726  		jlbl := javaString(env, l)
   727  		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setText, jvalue(jlbl)); err != nil {
   728  			return err
   729  		}
   730  	}
   731  	if d.Description != "" {
   732  		jd := javaString(env, d.Description)
   733  		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setContentDescription, jvalue(jd)); err != nil {
   734  			return err
   735  		}
   736  	}
   737  	addAction := func(act C.jint) {
   738  		if err := callVoidMethod(env, info, android.accessibilityNodeInfo.addAction, jvalue(act)); err != nil {
   739  			panic(err)
   740  		}
   741  	}
   742  	if d.Gestures&input.ClickGesture != 0 {
   743  		addAction(ACTION_CLICK)
   744  	}
   745  	clsName := android.strings.androidViewView
   746  	selectMethod := android.accessibilityNodeInfo.setChecked
   747  	checkable := false
   748  	switch d.Class {
   749  	case semantic.Button:
   750  		clsName = android.strings.androidWidgetButton
   751  	case semantic.CheckBox:
   752  		checkable = true
   753  		clsName = android.strings.androidWidgetCheckBox
   754  	case semantic.Editor:
   755  		clsName = android.strings.androidWidgetEditText
   756  	case semantic.RadioButton:
   757  		selectMethod = android.accessibilityNodeInfo.setSelected
   758  		clsName = android.strings.androidWidgetRadioButton
   759  	case semantic.Switch:
   760  		checkable = true
   761  		clsName = android.strings.androidWidgetSwitch
   762  	}
   763  	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setClassName, jvalue(clsName)); err != nil {
   764  		panic(err)
   765  	}
   766  	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setCheckable, jvalue(javaBool(checkable))); err != nil {
   767  		panic(err)
   768  	}
   769  	if err := callVoidMethod(env, info, selectMethod, jvalue(javaBool(d.Selected))); err != nil {
   770  		panic(err)
   771  	}
   772  	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setEnabled, jvalue(javaBool(!d.Disabled))); err != nil {
   773  		panic(err)
   774  	}
   775  	isFocus := w.semantic.focusID == sem.ID
   776  	if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setAccessibilityFocused, jvalue(javaBool(isFocus))); err != nil {
   777  		panic(err)
   778  	}
   779  	if isFocus {
   780  		addAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)
   781  	} else {
   782  		addAction(ACTION_ACCESSIBILITY_FOCUS)
   783  	}
   784  	return nil
   785  }
   786  
   787  func (w *window) virtualIDFor(id input.SemanticID) C.jint {
   788  	if id == w.semantic.rootID {
   789  		return HOST_VIEW_ID
   790  	}
   791  	return C.jint(id)
   792  }
   793  
   794  func (w *window) semIDFor(virtID C.jint) input.SemanticID {
   795  	if virtID == HOST_VIEW_ID {
   796  		return w.semantic.rootID
   797  	}
   798  	return input.SemanticID(virtID)
   799  }
   800  
   801  func (w *window) detach(env *C.JNIEnv) {
   802  	callVoidMethod(env, w.view, gioView.unregister)
   803  	w.processEvent(AndroidViewEvent{})
   804  	w.handle.Delete()
   805  	C.jni_DeleteGlobalRef(env, w.view)
   806  	w.view = 0
   807  }
   808  
   809  func (w *window) setVisible(env *C.JNIEnv) {
   810  	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
   811  	if width == 0 || height == 0 {
   812  		return
   813  	}
   814  	w.visible = true
   815  	w.draw(env, true)
   816  }
   817  
   818  func (w *window) setVisual(visID int) error {
   819  	if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
   820  		return errors.New("ANativeWindow_setBuffersGeometry failed")
   821  	}
   822  	return nil
   823  }
   824  
   825  func (w *window) nativeWindow() (*C.ANativeWindow, int, int) {
   826  	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
   827  	return w.win, int(width), int(height)
   828  }
   829  
   830  func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
   831  	dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity))
   832  	w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale))
   833  	switch dpi {
   834  	case C.ACONFIGURATION_DENSITY_NONE,
   835  		C.ACONFIGURATION_DENSITY_DEFAULT,
   836  		C.ACONFIGURATION_DENSITY_ANY:
   837  		// Assume standard density.
   838  		w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
   839  	default:
   840  		w.dpi = int(dpi)
   841  	}
   842  }
   843  
   844  func (w *window) SetAnimating(anim bool) {
   845  	w.animating = anim
   846  	if anim {
   847  		runInJVM(javaVM(), func(env *C.JNIEnv) {
   848  			callVoidMethod(env, w.view, gioView.postFrameCallback)
   849  		})
   850  	}
   851  }
   852  
   853  func (w *window) draw(env *C.JNIEnv, sync bool) {
   854  	if !w.visible {
   855  		return
   856  	}
   857  	size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
   858  	if size != w.config.Size {
   859  		w.config.Size = size
   860  		w.processEvent(ConfigEvent{Config: w.config})
   861  	}
   862  	if size.X == 0 || size.Y == 0 {
   863  		return
   864  	}
   865  	const inchPrDp = 1.0 / 160
   866  	ppdp := float32(w.dpi) * inchPrDp
   867  	dppp := unit.Dp(1.0 / ppdp)
   868  	insets := Insets{
   869  		Top:    unit.Dp(w.insets.top) * dppp,
   870  		Bottom: unit.Dp(w.insets.bottom) * dppp,
   871  		Left:   unit.Dp(w.insets.left) * dppp,
   872  		Right:  unit.Dp(w.insets.right) * dppp,
   873  	}
   874  	w.processEvent(frameEvent{
   875  		FrameEvent: FrameEvent{
   876  			Now:    time.Now(),
   877  			Size:   w.config.Size,
   878  			Insets: insets,
   879  			Metric: unit.Metric{
   880  				PxPerDp: ppdp,
   881  				PxPerSp: w.fontScale * ppdp,
   882  			},
   883  		},
   884  		Sync: sync,
   885  	})
   886  	a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
   887  	if err != nil {
   888  		panic(err)
   889  	}
   890  	if a11yActive {
   891  		if newR, oldR := w.callbacks.SemanticRoot(), w.semantic.rootID; newR != oldR {
   892  			// Remap focus and hover.
   893  			if oldR == w.semantic.hoverID {
   894  				w.semantic.hoverID = newR
   895  			}
   896  			if oldR == w.semantic.focusID {
   897  				w.semantic.focusID = newR
   898  			}
   899  			w.semantic.rootID = newR
   900  			callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR)))
   901  		}
   902  		w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0])
   903  		for _, id := range w.semantic.diffs {
   904  			callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id)))
   905  		}
   906  	}
   907  }
   908  
   909  func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
   910  	if jvm == nil {
   911  		panic("nil JVM")
   912  	}
   913  	runtime.LockOSThread()
   914  	defer runtime.UnlockOSThread()
   915  	var env *C.JNIEnv
   916  	if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
   917  		if res != C.JNI_EDETACHED {
   918  			panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
   919  		}
   920  		if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
   921  			panic(errors.New("runInJVM: AttachCurrentThread failed"))
   922  		}
   923  		defer C.jni_DetachCurrentThread(jvm)
   924  	}
   925  
   926  	f(env)
   927  }
   928  
   929  func convertKeyCode(code C.jint) (key.Name, bool) {
   930  	var n key.Name
   931  	switch code {
   932  	case C.AKEYCODE_FORWARD_DEL:
   933  		n = key.NameDeleteForward
   934  	case C.AKEYCODE_DEL:
   935  		n = key.NameDeleteBackward
   936  	case C.AKEYCODE_NUMPAD_ENTER:
   937  		n = key.NameEnter
   938  	case C.AKEYCODE_ENTER:
   939  		n = key.NameReturn
   940  	case C.AKEYCODE_CTRL_LEFT, C.AKEYCODE_CTRL_RIGHT:
   941  		n = key.NameCtrl
   942  	case C.AKEYCODE_SHIFT_LEFT, C.AKEYCODE_SHIFT_RIGHT:
   943  		n = key.NameShift
   944  	case C.AKEYCODE_ALT_LEFT, C.AKEYCODE_ALT_RIGHT:
   945  		n = key.NameAlt
   946  	case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT:
   947  		n = key.NameSuper
   948  	case C.AKEYCODE_DPAD_UP:
   949  		n = key.NameUpArrow
   950  	case C.AKEYCODE_DPAD_DOWN:
   951  		n = key.NameDownArrow
   952  	case C.AKEYCODE_DPAD_LEFT:
   953  		n = key.NameLeftArrow
   954  	case C.AKEYCODE_DPAD_RIGHT:
   955  		n = key.NameRightArrow
   956  	default:
   957  		return "", false
   958  	}
   959  	return n, true
   960  }
   961  
   962  //export Java_org_gioui_GioView_onKeyEvent
   963  func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) {
   964  	w := cgo.Handle(handle).Value().(*window)
   965  	if pressed == C.JNI_TRUE && keyCode == C.AKEYCODE_DPAD_CENTER {
   966  		w.callbacks.ClickFocus()
   967  		return
   968  	}
   969  	if n, ok := convertKeyCode(keyCode); ok {
   970  		state := key.Release
   971  		if pressed == C.JNI_TRUE {
   972  			state = key.Press
   973  		}
   974  		w.processEvent(key.Event{Name: n, State: state})
   975  	}
   976  	if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
   977  		w.callbacks.EditorInsert(string(rune(r)))
   978  	}
   979  }
   980  
   981  //export Java_org_gioui_GioView_onTouchEvent
   982  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) {
   983  	w := cgo.Handle(handle).Value().(*window)
   984  	var kind pointer.Kind
   985  	switch action {
   986  	case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
   987  		kind = pointer.Press
   988  	case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
   989  		kind = pointer.Release
   990  	case C.AMOTION_EVENT_ACTION_CANCEL:
   991  		kind = pointer.Cancel
   992  	case C.AMOTION_EVENT_ACTION_MOVE:
   993  		kind = pointer.Move
   994  	case C.AMOTION_EVENT_ACTION_SCROLL:
   995  		kind = pointer.Scroll
   996  	default:
   997  		return
   998  	}
   999  	var src pointer.Source
  1000  	var btns pointer.Buttons
  1001  	if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
  1002  		btns |= pointer.ButtonPrimary
  1003  	}
  1004  	if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
  1005  		btns |= pointer.ButtonSecondary
  1006  	}
  1007  	if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
  1008  		btns |= pointer.ButtonTertiary
  1009  	}
  1010  	switch tool {
  1011  	case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
  1012  		src = pointer.Touch
  1013  	case C.AMOTION_EVENT_TOOL_TYPE_STYLUS:
  1014  		src = pointer.Touch
  1015  	case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
  1016  		src = pointer.Mouse
  1017  	case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
  1018  		// For example, triggered via 'adb shell input tap'.
  1019  		// Instead of discarding it, treat it as a touch event.
  1020  		src = pointer.Touch
  1021  	default:
  1022  		return
  1023  	}
  1024  	w.processEvent(pointer.Event{
  1025  		Kind:      kind,
  1026  		Source:    src,
  1027  		Buttons:   btns,
  1028  		PointerID: pointer.ID(pointerID),
  1029  		Time:      time.Duration(t) * time.Millisecond,
  1030  		Position:  f32.Point{X: float32(x), Y: float32(y)},
  1031  		Scroll:    f32.Pt(float32(scrollX), float32(scrollY)),
  1032  	})
  1033  }
  1034  
  1035  //export Java_org_gioui_GioView_imeSelectionStart
  1036  func Java_org_gioui_GioView_imeSelectionStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
  1037  	w := cgo.Handle(handle).Value().(*window)
  1038  	sel := w.callbacks.EditorState().Selection
  1039  	start := sel.Start
  1040  	if sel.End < sel.Start {
  1041  		start = sel.End
  1042  	}
  1043  	return C.jint(start)
  1044  }
  1045  
  1046  //export Java_org_gioui_GioView_imeSelectionEnd
  1047  func Java_org_gioui_GioView_imeSelectionEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
  1048  	w := cgo.Handle(handle).Value().(*window)
  1049  	sel := w.callbacks.EditorState().Selection
  1050  	end := sel.End
  1051  	if sel.End < sel.Start {
  1052  		end = sel.Start
  1053  	}
  1054  	return C.jint(end)
  1055  }
  1056  
  1057  //export Java_org_gioui_GioView_imeComposingStart
  1058  func Java_org_gioui_GioView_imeComposingStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
  1059  	w := cgo.Handle(handle).Value().(*window)
  1060  	comp := w.callbacks.EditorState().compose
  1061  	start := comp.Start
  1062  	if e := comp.End; e < start {
  1063  		start = e
  1064  	}
  1065  	return C.jint(start)
  1066  }
  1067  
  1068  //export Java_org_gioui_GioView_imeComposingEnd
  1069  func Java_org_gioui_GioView_imeComposingEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
  1070  	w := cgo.Handle(handle).Value().(*window)
  1071  	comp := w.callbacks.EditorState().compose
  1072  	end := comp.End
  1073  	if s := comp.Start; s > end {
  1074  		end = s
  1075  	}
  1076  	return C.jint(end)
  1077  }
  1078  
  1079  //export Java_org_gioui_GioView_imeSnippet
  1080  func Java_org_gioui_GioView_imeSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jstring {
  1081  	w := cgo.Handle(handle).Value().(*window)
  1082  	snip := w.callbacks.EditorState().Snippet.Text
  1083  	return javaString(env, snip)
  1084  }
  1085  
  1086  //export Java_org_gioui_GioView_imeSnippetStart
  1087  func Java_org_gioui_GioView_imeSnippetStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint {
  1088  	w := cgo.Handle(handle).Value().(*window)
  1089  	return C.jint(w.callbacks.EditorState().Snippet.Start)
  1090  }
  1091  
  1092  //export Java_org_gioui_GioView_imeSetSnippet
  1093  func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
  1094  	w := cgo.Handle(handle).Value().(*window)
  1095  	if start < 0 {
  1096  		start = 0
  1097  	}
  1098  	if end < start {
  1099  		end = start
  1100  	}
  1101  	r := key.Range{Start: int(start), End: int(end)}
  1102  	w.callbacks.SetEditorSnippet(r)
  1103  }
  1104  
  1105  //export Java_org_gioui_GioView_imeSetSelection
  1106  func Java_org_gioui_GioView_imeSetSelection(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
  1107  	w := cgo.Handle(handle).Value().(*window)
  1108  	r := key.Range{Start: int(start), End: int(end)}
  1109  	w.callbacks.SetEditorSelection(r)
  1110  }
  1111  
  1112  //export Java_org_gioui_GioView_imeSetComposingRegion
  1113  func Java_org_gioui_GioView_imeSetComposingRegion(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) {
  1114  	w := cgo.Handle(handle).Value().(*window)
  1115  	w.callbacks.SetComposingRegion(key.Range{
  1116  		Start: int(start),
  1117  		End:   int(end),
  1118  	})
  1119  }
  1120  
  1121  //export Java_org_gioui_GioView_imeReplace
  1122  func Java_org_gioui_GioView_imeReplace(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint, jtext C.jstring) {
  1123  	w := cgo.Handle(handle).Value().(*window)
  1124  	r := key.Range{Start: int(start), End: int(end)}
  1125  	text := goString(env, jtext)
  1126  	w.callbacks.EditorReplace(r, text)
  1127  }
  1128  
  1129  //export Java_org_gioui_GioView_imeToRunes
  1130  func Java_org_gioui_GioView_imeToRunes(env *C.JNIEnv, class C.jclass, handle C.jlong, chars C.jint) C.jint {
  1131  	w := cgo.Handle(handle).Value().(*window)
  1132  	state := w.callbacks.EditorState()
  1133  	return C.jint(state.RunesIndex(int(chars)))
  1134  }
  1135  
  1136  //export Java_org_gioui_GioView_imeToUTF16
  1137  func Java_org_gioui_GioView_imeToUTF16(env *C.JNIEnv, class C.jclass, handle C.jlong, runes C.jint) C.jint {
  1138  	w := cgo.Handle(handle).Value().(*window)
  1139  	state := w.callbacks.EditorState()
  1140  	return C.jint(state.UTF16Index(int(runes)))
  1141  }
  1142  
  1143  func (w *window) EditorStateChanged(old, new editorState) {
  1144  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1145  		if old.Snippet != new.Snippet {
  1146  			callVoidMethod(env, w.view, gioView.restartInput)
  1147  			return
  1148  		}
  1149  		if old.Selection.Range != new.Selection.Range {
  1150  			w.callbacks.SetComposingRegion(key.Range{Start: -1, End: -1})
  1151  			callVoidMethod(env, w.view, gioView.updateSelection)
  1152  		}
  1153  		if old.Selection.Transform != new.Selection.Transform || old.Selection.Caret != new.Selection.Caret {
  1154  			sel := new.Selection
  1155  			m00, m01, m02, m10, m11, m12 := sel.Transform.Elems()
  1156  			f := func(v float32) jvalue {
  1157  				return jvalue(math.Float32bits(v))
  1158  			}
  1159  			c := sel.Caret
  1160  			callVoidMethod(env, w.view, gioView.updateCaret, f(m00), f(m01), f(m02), f(m10), f(m11), f(m12), f(c.Pos.X), f(c.Pos.Y-c.Ascent), f(c.Pos.Y), f(c.Pos.Y+c.Descent))
  1161  		}
  1162  	})
  1163  }
  1164  
  1165  func (w *window) ShowTextInput(show bool) {
  1166  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1167  		if show {
  1168  			callVoidMethod(env, w.view, gioView.showTextInput)
  1169  		} else {
  1170  			callVoidMethod(env, w.view, gioView.hideTextInput)
  1171  		}
  1172  	})
  1173  }
  1174  
  1175  func (w *window) SetInputHint(mode key.InputHint) {
  1176  	w.inputHint = mode
  1177  
  1178  	// Constants defined at https://developer.android.com/reference/android/text/InputType.
  1179  	const (
  1180  		TYPE_NULL = 0
  1181  
  1182  		TYPE_CLASS_TEXT                   = 1
  1183  		TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32
  1184  		TYPE_TEXT_VARIATION_URI           = 16
  1185  		TYPE_TEXT_VARIATION_PASSWORD      = 128
  1186  		TYPE_TEXT_FLAG_CAP_SENTENCES      = 16384
  1187  		TYPE_TEXT_FLAG_AUTO_CORRECT       = 32768
  1188  
  1189  		TYPE_CLASS_NUMBER        = 2
  1190  		TYPE_NUMBER_FLAG_DECIMAL = 8192
  1191  		TYPE_NUMBER_FLAG_SIGNED  = 4096
  1192  
  1193  		TYPE_CLASS_PHONE = 3
  1194  	)
  1195  
  1196  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1197  		var m jvalue
  1198  		switch mode {
  1199  		case key.HintText:
  1200  			m = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_CAP_SENTENCES
  1201  		case key.HintNumeric:
  1202  			m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
  1203  		case key.HintEmail:
  1204  			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS
  1205  		case key.HintURL:
  1206  			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI
  1207  		case key.HintTelephone:
  1208  			m = TYPE_CLASS_PHONE
  1209  		case key.HintPassword:
  1210  			m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD
  1211  		default:
  1212  			m = TYPE_CLASS_TEXT
  1213  		}
  1214  
  1215  		callVoidMethod(env, w.view, gioView.setInputHint, m)
  1216  	})
  1217  }
  1218  
  1219  func javaBool(b bool) C.jboolean {
  1220  	if b {
  1221  		return C.JNI_TRUE
  1222  	} else {
  1223  		return C.JNI_FALSE
  1224  	}
  1225  }
  1226  
  1227  func javaString(env *C.JNIEnv, str string) C.jstring {
  1228  	utf16Chars := utf16.Encode([]rune(str))
  1229  	var ptr *C.jchar
  1230  	if len(utf16Chars) > 0 {
  1231  		ptr = (*C.jchar)(unsafe.Pointer(&utf16Chars[0]))
  1232  	}
  1233  	return C.jni_NewString(env, ptr, C.int(len(utf16Chars)))
  1234  }
  1235  
  1236  func varArgs(args []jvalue) *C.jvalue {
  1237  	if len(args) == 0 {
  1238  		return nil
  1239  	}
  1240  	return (*C.jvalue)(unsafe.Pointer(&args[0]))
  1241  }
  1242  
  1243  func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
  1244  	C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
  1245  	return exception(env)
  1246  }
  1247  
  1248  func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
  1249  	res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
  1250  	return res, exception(env)
  1251  }
  1252  
  1253  func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
  1254  	C.jni_CallVoidMethodA(env, obj, method, varArgs(args))
  1255  	return exception(env)
  1256  }
  1257  
  1258  func callBooleanMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (bool, error) {
  1259  	res := C.jni_CallBooleanMethodA(env, obj, method, varArgs(args))
  1260  	return res == C.JNI_TRUE, exception(env)
  1261  }
  1262  
  1263  func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
  1264  	res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args))
  1265  	return res, exception(env)
  1266  }
  1267  
  1268  func newObject(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
  1269  	res := C.jni_NewObjectA(env, cls, method, varArgs(args))
  1270  	return res, exception(env)
  1271  }
  1272  
  1273  // exception returns an error corresponding to the pending
  1274  // exception, or nil if no exception is pending. The pending
  1275  // exception is cleared.
  1276  func exception(env *C.JNIEnv) error {
  1277  	thr := C.jni_ExceptionOccurred(env)
  1278  	if thr == 0 {
  1279  		return nil
  1280  	}
  1281  	C.jni_ExceptionClear(env)
  1282  	cls := getObjectClass(env, C.jobject(thr))
  1283  	toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
  1284  	msg, err := callObjectMethod(env, C.jobject(thr), toString)
  1285  	if err != nil {
  1286  		return err
  1287  	}
  1288  	return errors.New(goString(env, C.jstring(msg)))
  1289  }
  1290  
  1291  func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
  1292  	if obj == 0 {
  1293  		panic("null object")
  1294  	}
  1295  	cls := C.jni_GetObjectClass(env, C.jobject(obj))
  1296  	if err := exception(env); err != nil {
  1297  		// GetObjectClass should never fail.
  1298  		panic(err)
  1299  	}
  1300  	return cls
  1301  }
  1302  
  1303  // goString converts the JVM jstring to a Go string.
  1304  func goString(env *C.JNIEnv, str C.jstring) string {
  1305  	if str == 0 {
  1306  		return ""
  1307  	}
  1308  	strlen := C.jni_GetStringLength(env, C.jstring(str))
  1309  	chars := C.jni_GetStringChars(env, C.jstring(str))
  1310  	utf16Chars := unsafe.Slice((*uint16)(unsafe.Pointer(chars)), strlen)
  1311  	utf8 := utf16.Decode(utf16Chars)
  1312  	return string(utf8)
  1313  }
  1314  
  1315  func findClass(env *C.JNIEnv, name string) C.jclass {
  1316  	cn := C.CString(name)
  1317  	defer C.free(unsafe.Pointer(cn))
  1318  	return C.jni_FindClass(env, cn)
  1319  }
  1320  
  1321  func osMain() {
  1322  }
  1323  
  1324  func newWindow(window *callbacks, options []Option) {
  1325  	mainWindow.in <- windowAndConfig{window, options}
  1326  	<-mainWindow.windows
  1327  }
  1328  
  1329  func (w *window) WriteClipboard(mime string, s []byte) {
  1330  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1331  		jstr := javaString(env, string(s))
  1332  		callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
  1333  			jvalue(android.appCtx), jvalue(jstr))
  1334  	})
  1335  }
  1336  
  1337  func (w *window) ReadClipboard() {
  1338  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1339  		c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
  1340  			jvalue(android.appCtx))
  1341  		if err != nil {
  1342  			return
  1343  		}
  1344  		content := goString(env, C.jstring(c))
  1345  		w.processEvent(transfer.DataEvent{
  1346  			Type: "application/text",
  1347  			Open: func() io.ReadCloser {
  1348  				return io.NopCloser(strings.NewReader(content))
  1349  			},
  1350  		})
  1351  	})
  1352  }
  1353  
  1354  func (w *window) Configure(options []Option) {
  1355  	cnf := w.config
  1356  	cnf.apply(unit.Metric{}, options)
  1357  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1358  		w.setConfig(env, cnf)
  1359  	})
  1360  }
  1361  
  1362  func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
  1363  	prev := w.config
  1364  	// Decorations are never disabled.
  1365  	cnf.Decorated = true
  1366  
  1367  	if prev.Orientation != cnf.Orientation {
  1368  		w.config.Orientation = cnf.Orientation
  1369  		setOrientation(env, w.view, cnf.Orientation)
  1370  	}
  1371  	if prev.NavigationColor != cnf.NavigationColor {
  1372  		w.config.NavigationColor = cnf.NavigationColor
  1373  		setNavigationColor(env, w.view, cnf.NavigationColor)
  1374  	}
  1375  	if prev.StatusColor != cnf.StatusColor {
  1376  		w.config.StatusColor = cnf.StatusColor
  1377  		setStatusColor(env, w.view, cnf.StatusColor)
  1378  	}
  1379  	if prev.Mode != cnf.Mode {
  1380  		switch cnf.Mode {
  1381  		case Fullscreen:
  1382  			callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
  1383  			w.config.Mode = Fullscreen
  1384  		case Windowed:
  1385  			callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
  1386  			w.config.Mode = Windowed
  1387  		}
  1388  	}
  1389  	if cnf.Decorated != prev.Decorated {
  1390  		w.config.Decorated = cnf.Decorated
  1391  	}
  1392  	w.processEvent(ConfigEvent{Config: w.config})
  1393  }
  1394  
  1395  func (w *window) Perform(system.Action) {}
  1396  
  1397  func (w *window) SetCursor(cursor pointer.Cursor) {
  1398  	runInJVM(javaVM(), func(env *C.JNIEnv) {
  1399  		setCursor(env, w.view, cursor)
  1400  	})
  1401  }
  1402  
  1403  func (w *window) wakeup() {
  1404  	runOnMain(func(env *C.JNIEnv) {
  1405  		w.loop.Wakeup()
  1406  		w.loop.FlushEvents()
  1407  	})
  1408  }
  1409  
  1410  var androidCursor = [...]uint16{
  1411  	pointer.CursorDefault:                  1000, // TYPE_ARROW
  1412  	pointer.CursorNone:                     0,
  1413  	pointer.CursorText:                     1008, // TYPE_TEXT
  1414  	pointer.CursorVerticalText:             1009, // TYPE_VERTICAL_TEXT
  1415  	pointer.CursorPointer:                  1002, // TYPE_HAND
  1416  	pointer.CursorCrosshair:                1007, // TYPE_CROSSHAIR
  1417  	pointer.CursorAllScroll:                1013, // TYPE_ALL_SCROLL
  1418  	pointer.CursorColResize:                1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
  1419  	pointer.CursorRowResize:                1015, // TYPE_VERTICAL_DOUBLE_ARROW
  1420  	pointer.CursorGrab:                     1020, // TYPE_GRAB
  1421  	pointer.CursorGrabbing:                 1021, // TYPE_GRABBING
  1422  	pointer.CursorNotAllowed:               1012, // TYPE_NO_DROP
  1423  	pointer.CursorWait:                     1004, // TYPE_WAIT
  1424  	pointer.CursorProgress:                 1000, // TYPE_ARROW
  1425  	pointer.CursorNorthWestResize:          1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
  1426  	pointer.CursorNorthEastResize:          1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
  1427  	pointer.CursorSouthWestResize:          1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
  1428  	pointer.CursorSouthEastResize:          1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
  1429  	pointer.CursorNorthSouthResize:         1015, // TYPE_VERTICAL_DOUBLE_ARROW
  1430  	pointer.CursorEastWestResize:           1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
  1431  	pointer.CursorWestResize:               1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
  1432  	pointer.CursorEastResize:               1014, // TYPE_HORIZONTAL_DOUBLE_ARROW
  1433  	pointer.CursorNorthResize:              1015, // TYPE_VERTICAL_DOUBLE_ARROW
  1434  	pointer.CursorSouthResize:              1015, // TYPE_VERTICAL_DOUBLE_ARROW
  1435  	pointer.CursorNorthEastSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
  1436  	pointer.CursorNorthWestSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
  1437  }
  1438  
  1439  func setCursor(env *C.JNIEnv, view C.jobject, cursor pointer.Cursor) {
  1440  	curID := androidCursor[cursor]
  1441  	callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
  1442  }
  1443  
  1444  func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
  1445  	var (
  1446  		id         int
  1447  		idFallback int // Used only for SDK 17 or older.
  1448  	)
  1449  	// Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
  1450  	switch mode {
  1451  	case AnyOrientation:
  1452  		id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
  1453  	case LandscapeOrientation:
  1454  		id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
  1455  	case PortraitOrientation:
  1456  		id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
  1457  	}
  1458  	callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
  1459  }
  1460  
  1461  func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
  1462  	callVoidMethod(env, view, gioView.setStatusColor,
  1463  		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
  1464  		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
  1465  	)
  1466  }
  1467  
  1468  func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
  1469  	callVoidMethod(env, view, gioView.setNavigationColor,
  1470  		jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
  1471  		jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
  1472  	)
  1473  }
  1474  
  1475  // runOnMain runs a function on the Java main thread.
  1476  func runOnMain(f func(env *C.JNIEnv)) {
  1477  	go func() {
  1478  		mainFuncs <- f
  1479  		runInJVM(javaVM(), func(env *C.JNIEnv) {
  1480  			callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
  1481  		})
  1482  	}()
  1483  }
  1484  
  1485  //export Java_org_gioui_Gio_scheduleMainFuncs
  1486  func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
  1487  	for {
  1488  		select {
  1489  		case f := <-mainFuncs:
  1490  			f(env)
  1491  		default:
  1492  			return
  1493  		}
  1494  	}
  1495  }
  1496  
  1497  func (AndroidViewEvent) implementsViewEvent() {}
  1498  func (AndroidViewEvent) ImplementsEvent()     {}