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