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