aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/app/os_android.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/app/os_android.go')
-rw-r--r--vendor/gioui.org/app/os_android.go1283
1 files changed, 1283 insertions, 0 deletions
diff --git a/vendor/gioui.org/app/os_android.go b/vendor/gioui.org/app/os_android.go
new file mode 100644
index 0000000..51d8289
--- /dev/null
+++ b/vendor/gioui.org/app/os_android.go
@@ -0,0 +1,1283 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package app
+
+/*
+#cgo CFLAGS: -Werror
+#cgo LDFLAGS: -landroid
+
+#include <android/native_window_jni.h>
+#include <android/configuration.h>
+#include <android/keycodes.h>
+#include <android/input.h>
+#include <stdlib.h>
+
+static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
+ return (*vm)->GetEnv(vm, (void **)env, version);
+}
+
+static jint jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
+ return (*env)->GetJavaVM(env, jvm);
+}
+
+static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
+ return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
+}
+
+static jint jni_DetachCurrentThread(JavaVM *vm) {
+ return (*vm)->DetachCurrentThread(vm);
+}
+
+static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) {
+ return (*env)->NewGlobalRef(env, obj);
+}
+
+static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
+ (*env)->DeleteGlobalRef(env, obj);
+}
+
+static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) {
+ return (*env)->GetObjectClass(env, obj);
+}
+
+static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
+ return (*env)->GetMethodID(env, clazz, name, sig);
+}
+
+static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
+ return (*env)->GetStaticMethodID(env, clazz, name, sig);
+}
+
+static jfloat jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+ return (*env)->CallFloatMethod(env, obj, methodID);
+}
+
+static jint jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+ return (*env)->CallIntMethod(env, obj, methodID);
+}
+
+static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
+ (*env)->CallStaticVoidMethodA(env, cls, methodID, args);
+}
+
+static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
+ (*env)->CallVoidMethodA(env, obj, methodID, args);
+}
+
+static jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
+ return (*env)->CallBooleanMethodA(env, obj, methodID, args);
+}
+
+static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
+ return (*env)->GetByteArrayElements(env, arr, NULL);
+}
+
+static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
+ (*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
+}
+
+static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
+ return (*env)->GetArrayLength(env, arr);
+}
+
+static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
+ return (*env)->NewString(env, unicodeChars, len);
+}
+
+static jsize jni_GetStringLength(JNIEnv *env, jstring str) {
+ return (*env)->GetStringLength(env, str);
+}
+
+static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) {
+ return (*env)->GetStringChars(env, str, NULL);
+}
+
+static jthrowable jni_ExceptionOccurred(JNIEnv *env) {
+ return (*env)->ExceptionOccurred(env);
+}
+
+static void jni_ExceptionClear(JNIEnv *env) {
+ (*env)->ExceptionClear(env);
+}
+
+static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
+ return (*env)->CallObjectMethodA(env, obj, method, args);
+}
+
+static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
+ return (*env)->CallStaticObjectMethodA(env, cls, method, args);
+}
+
+static jclass jni_FindClass(JNIEnv *env, char *name) {
+ return (*env)->FindClass(env, name);
+}
+
+static jobject jni_NewObjectA(JNIEnv *env, jclass cls, jmethodID cons, jvalue *args) {
+ return (*env)->NewObjectA(env, cls, cons, args);
+}
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "image"
+ "image/color"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "runtime/debug"
+ "sync"
+ "time"
+ "unicode/utf16"
+ "unsafe"
+
+ "gioui.org/internal/f32color"
+
+ "gioui.org/f32"
+ "gioui.org/io/clipboard"
+ "gioui.org/io/key"
+ "gioui.org/io/pointer"
+ "gioui.org/io/router"
+ "gioui.org/io/semantic"
+ "gioui.org/io/system"
+ "gioui.org/unit"
+)
+
+type window struct {
+ callbacks *callbacks
+
+ view C.jobject
+
+ dpi int
+ fontScale float32
+ insets system.Insets
+
+ stage system.Stage
+ started bool
+ animating bool
+
+ win *C.ANativeWindow
+ config Config
+
+ semantic struct {
+ hoverID router.SemanticID
+ rootID router.SemanticID
+ focusID router.SemanticID
+ diffs []router.SemanticID
+ }
+}
+
+// gioView hold cached JNI methods for GioView.
+var gioView struct {
+ once sync.Once
+ getDensity C.jmethodID
+ getFontScale C.jmethodID
+ showTextInput C.jmethodID
+ hideTextInput C.jmethodID
+ setInputHint C.jmethodID
+ postInvalidate C.jmethodID // requests draw, called from non-UI thread
+ invalidate C.jmethodID // requests draw, called from UI thread
+ setCursor C.jmethodID
+ setOrientation C.jmethodID
+ setNavigationColor C.jmethodID
+ setStatusColor C.jmethodID
+ setFullscreen C.jmethodID
+ unregister C.jmethodID
+ sendA11yEvent C.jmethodID
+ sendA11yChange C.jmethodID
+ isA11yActive C.jmethodID
+}
+
+// ViewEvent is sent whenever the Window's underlying Android view
+// changes.
+type ViewEvent struct {
+ // View is a JNI global reference to the android.view.View
+ // instance backing the Window. The reference is valid until
+ // the next ViewEvent is received.
+ // A zero View means that there is currently no view attached.
+ View uintptr
+}
+
+type jvalue uint64 // The largest JNI type fits in 64 bits.
+
+var dataDirChan = make(chan string, 1)
+
+var android struct {
+ // mu protects all fields of this structure. However, once a
+ // non-nil jvm is returned from javaVM, all the other fields may
+ // be accessed unlocked.
+ mu sync.Mutex
+ jvm *C.JavaVM
+
+ // appCtx is the global Android App context.
+ appCtx C.jobject
+ // gioCls is the class of the Gio class.
+ gioCls C.jclass
+
+ mwriteClipboard C.jmethodID
+ mreadClipboard C.jmethodID
+ mwakeupMainThread C.jmethodID
+
+ // android.view.accessibility.AccessibilityNodeInfo class.
+ accessibilityNodeInfo struct {
+ cls C.jclass
+ // addChild(View, int)
+ addChild C.jmethodID
+ // setBoundsInScreen(Rect)
+ setBoundsInScreen C.jmethodID
+ // setText(CharSequence)
+ setText C.jmethodID
+ // setContentDescription(CharSequence)
+ setContentDescription C.jmethodID
+ // setParent(View, int)
+ setParent C.jmethodID
+ // addAction(int)
+ addAction C.jmethodID
+ // setClassName(CharSequence)
+ setClassName C.jmethodID
+ // setCheckable(boolean)
+ setCheckable C.jmethodID
+ // setSelected(boolean)
+ setSelected C.jmethodID
+ // setChecked(boolean)
+ setChecked C.jmethodID
+ // setEnabled(boolean)
+ setEnabled C.jmethodID
+ // setAccessibilityFocused(boolean)
+ setAccessibilityFocused C.jmethodID
+ }
+
+ // android.graphics.Rect class.
+ rect struct {
+ cls C.jclass
+ // (int, int, int, int) constructor.
+ cons C.jmethodID
+ }
+
+ strings struct {
+ // "android.view.View"
+ androidViewView C.jstring
+ // "android.widget.Button"
+ androidWidgetButton C.jstring
+ // "android.widget.CheckBox"
+ androidWidgetCheckBox C.jstring
+ // "android.widget.EditText"
+ androidWidgetEditText C.jstring
+ // "android.widget.RadioButton"
+ androidWidgetRadioButton C.jstring
+ // "android.widget.Switch"
+ androidWidgetSwitch C.jstring
+ }
+}
+
+// view maps from GioView JNI refenreces to windows.
+var views = make(map[C.jlong]*window)
+
+var windows = make(map[*callbacks]*window)
+
+var mainWindow = newWindowRendezvous()
+
+var mainFuncs = make(chan func(env *C.JNIEnv), 1)
+
+var (
+ dataDirOnce sync.Once
+ dataPath string
+)
+
+var (
+ newAndroidVulkanContext func(w *window) (context, error)
+ newAndroidGLESContext func(w *window) (context, error)
+)
+
+// AccessibilityNodeProvider.HOST_VIEW_ID.
+const HOST_VIEW_ID = -1
+
+const (
+ // AccessibilityEvent constants.
+ TYPE_VIEW_HOVER_ENTER = 128
+ TYPE_VIEW_HOVER_EXIT = 256
+)
+
+const (
+ // AccessibilityNodeInfo constants.
+ ACTION_ACCESSIBILITY_FOCUS = 64
+ ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128
+ ACTION_CLICK = 16
+)
+
+func (w *window) NewContext() (context, error) {
+ funcs := []func(w *window) (context, error){newAndroidVulkanContext, newAndroidGLESContext}
+ var firstErr error
+ for _, f := range funcs {
+ if f == nil {
+ continue
+ }
+ c, err := f(w)
+ if err != nil {
+ if firstErr == nil {
+ firstErr = err
+ }
+ continue
+ }
+ return c, nil
+ }
+ if firstErr != nil {
+ return nil, firstErr
+ }
+ return nil, errors.New("x11: no available GPU backends")
+}
+
+func dataDir() (string, error) {
+ dataDirOnce.Do(func() {
+ dataPath = <-dataDirChan
+ // Set XDG_CACHE_HOME to make os.UserCacheDir work.
+ if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
+ cachePath := filepath.Join(dataPath, "cache")
+ os.Setenv("XDG_CACHE_HOME", cachePath)
+ }
+ // Set XDG_CONFIG_HOME to make os.UserConfigDir work.
+ if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
+ cfgPath := filepath.Join(dataPath, "config")
+ os.Setenv("XDG_CONFIG_HOME", cfgPath)
+ }
+ // Set HOME to make os.UserHomeDir work.
+ if _, exists := os.LookupEnv("HOME"); !exists {
+ os.Setenv("HOME", dataPath)
+ }
+ })
+ return dataPath, nil
+}
+
+func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
+ m := C.CString(method)
+ defer C.free(unsafe.Pointer(m))
+ s := C.CString(sig)
+ defer C.free(unsafe.Pointer(s))
+ jm := C.jni_GetMethodID(env, class, m, s)
+ if err := exception(env); err != nil {
+ panic(err)
+ }
+ return jm
+}
+
+func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
+ m := C.CString(method)
+ defer C.free(unsafe.Pointer(m))
+ s := C.CString(sig)
+ defer C.free(unsafe.Pointer(s))
+ jm := C.jni_GetStaticMethodID(env, class, m, s)
+ if err := exception(env); err != nil {
+ panic(err)
+ }
+ return jm
+}
+
+//export Java_org_gioui_Gio_runGoMain
+func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
+ initJVM(env, class, context)
+ dirBytes := C.jni_GetByteArrayElements(env, jdataDir)
+ if dirBytes == nil {
+ panic("runGoMain: GetByteArrayElements failed")
+ }
+ n := C.jni_GetArrayLength(env, jdataDir)
+ dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
+ dataDirChan <- dataDir
+ C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
+
+ runMain()
+}
+
+func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
+ android.mu.Lock()
+ defer android.mu.Unlock()
+ if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 {
+ panic("gio: GetJavaVM failed")
+ }
+ android.appCtx = C.jni_NewGlobalRef(env, ctx)
+ android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio)))
+
+ cls := findClass(env, "android/view/accessibility/AccessibilityNodeInfo")
+ android.accessibilityNodeInfo.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
+ android.accessibilityNodeInfo.addChild = getMethodID(env, cls, "addChild", "(Landroid/view/View;I)V")
+ android.accessibilityNodeInfo.setBoundsInScreen = getMethodID(env, cls, "setBoundsInScreen", "(Landroid/graphics/Rect;)V")
+ android.accessibilityNodeInfo.setText = getMethodID(env, cls, "setText", "(Ljava/lang/CharSequence;)V")
+ android.accessibilityNodeInfo.setContentDescription = getMethodID(env, cls, "setContentDescription", "(Ljava/lang/CharSequence;)V")
+ android.accessibilityNodeInfo.setParent = getMethodID(env, cls, "setParent", "(Landroid/view/View;I)V")
+ android.accessibilityNodeInfo.addAction = getMethodID(env, cls, "addAction", "(I)V")
+ android.accessibilityNodeInfo.setClassName = getMethodID(env, cls, "setClassName", "(Ljava/lang/CharSequence;)V")
+ android.accessibilityNodeInfo.setCheckable = getMethodID(env, cls, "setCheckable", "(Z)V")
+ android.accessibilityNodeInfo.setSelected = getMethodID(env, cls, "setSelected", "(Z)V")
+ android.accessibilityNodeInfo.setChecked = getMethodID(env, cls, "setChecked", "(Z)V")
+ android.accessibilityNodeInfo.setEnabled = getMethodID(env, cls, "setEnabled", "(Z)V")
+ android.accessibilityNodeInfo.setAccessibilityFocused = getMethodID(env, cls, "setAccessibilityFocused", "(Z)V")
+
+ cls = findClass(env, "android/graphics/Rect")
+ android.rect.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls)))
+ android.rect.cons = getMethodID(env, cls, "<init>", "(IIII)V")
+ android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
+ android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
+ android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
+
+ intern := func(s string) C.jstring {
+ ref := C.jni_NewGlobalRef(env, C.jobject(javaString(env, s)))
+ return C.jstring(ref)
+ }
+ android.strings.androidViewView = intern("android.view.View")
+ android.strings.androidWidgetButton = intern("android.widget.Button")
+ android.strings.androidWidgetCheckBox = intern("android.widget.CheckBox")
+ android.strings.androidWidgetEditText = intern("android.widget.EditText")
+ android.strings.androidWidgetRadioButton = intern("android.widget.RadioButton")
+ android.strings.androidWidgetSwitch = intern("android.widget.Switch")
+}
+
+// JavaVM returns the global JNI JavaVM.
+func JavaVM() uintptr {
+ jvm := javaVM()
+ return uintptr(unsafe.Pointer(jvm))
+}
+
+func javaVM() *C.JavaVM {
+ android.mu.Lock()
+ defer android.mu.Unlock()
+ return android.jvm
+}
+
+// AppContext returns the global Application context as a JNI jobject.
+func AppContext() uintptr {
+ android.mu.Lock()
+ defer android.mu.Unlock()
+ return uintptr(android.appCtx)
+}
+
+//export Java_org_gioui_GioView_onCreateView
+func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
+ gioView.once.Do(func() {
+ m := &gioView
+ m.getDensity = getMethodID(env, class, "getDensity", "()I")
+ m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
+ m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
+ m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
+ m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V")
+ m.postInvalidate = getMethodID(env, class, "postInvalidate", "()V")
+ m.invalidate = getMethodID(env, class, "invalidate", "()V")
+ m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
+ m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V")
+ m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V")
+ m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V")
+ m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V")
+ m.unregister = getMethodID(env, class, "unregister", "()V")
+ m.sendA11yEvent = getMethodID(env, class, "sendA11yEvent", "(II)V")
+ m.sendA11yChange = getMethodID(env, class, "sendA11yChange", "(I)V")
+ m.isA11yActive = getMethodID(env, class, "isA11yActive", "()Z")
+ })
+ view = C.jni_NewGlobalRef(env, view)
+ wopts := <-mainWindow.out
+ w, ok := windows[wopts.window]
+ if !ok {
+ w = &window{
+ callbacks: wopts.window,
+ }
+ windows[wopts.window] = w
+ }
+ if w.view != 0 {
+ w.detach(env)
+ }
+ w.view = view
+ w.callbacks.SetDriver(w)
+ handle := C.jlong(view)
+ views[handle] = w
+ w.loadConfig(env, class)
+ w.Configure(wopts.options)
+ w.setStage(system.StagePaused)
+ w.callbacks.Event(ViewEvent{View: uintptr(view)})
+ return handle
+}
+
+//export Java_org_gioui_GioView_onDestroyView
+func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+ w := views[handle]
+ w.detach(env)
+}
+
+//export Java_org_gioui_GioView_onStopView
+func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+ w := views[handle]
+ w.started = false
+ w.setStage(system.StagePaused)
+}
+
+//export Java_org_gioui_GioView_onStartView
+func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+ w := views[handle]
+ w.started = true
+ if w.win != nil {
+ w.setVisible(env)
+ }
+}
+
+//export Java_org_gioui_GioView_onSurfaceDestroyed
+func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
+ w := views[handle]
+ w.win = nil
+ w.setStage(system.StagePaused)
+}
+
+//export Java_org_gioui_GioView_onSurfaceChanged
+func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
+ w := views[handle]
+ w.win = C.ANativeWindow_fromSurface(env, surf)
+ if w.started {
+ w.setVisible(env)
+ }
+}
+
+//export Java_org_gioui_GioView_onLowMemory
+func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
+ runtime.GC()
+ debug.FreeOSMemory()
+}
+
+//export Java_org_gioui_GioView_onConfigurationChanged
+func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
+ w := views[view]
+ w.loadConfig(env, class)
+ if w.stage >= system.StageRunning {
+ w.draw(env, true)
+ }
+}
+
+//export Java_org_gioui_GioView_onFrameCallback
+func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong) {
+ w, exist := views[view]
+ if !exist {
+ return
+ }
+ if w.stage < system.StageRunning {
+ return
+ }
+ if w.animating {
+ w.draw(env, false)
+ // Schedule the next draw immediately after this one. Since onFrameCallback runs
+ // on the UI thread, View.invalidate can be used here instead of postInvalidate.
+ callVoidMethod(env, w.view, gioView.invalidate)
+ }
+}
+
+//export Java_org_gioui_GioView_onBack
+func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
+ w := views[view]
+ ev := &system.CommandEvent{Type: system.CommandBack}
+ w.callbacks.Event(ev)
+ if ev.Cancel {
+ return C.JNI_TRUE
+ }
+ return C.JNI_FALSE
+}
+
+//export Java_org_gioui_GioView_onFocusChange
+func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
+ w := views[view]
+ w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
+}
+
+//export Java_org_gioui_GioView_onWindowInsets
+func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
+ w := views[view]
+ w.insets = system.Insets{
+ Top: unit.Px(float32(top)),
+ Right: unit.Px(float32(right)),
+ Bottom: unit.Px(float32(bottom)),
+ Left: unit.Px(float32(left)),
+ }
+ if w.stage >= system.StageRunning {
+ w.draw(env, true)
+ }
+}
+
+//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
+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 {
+ w := views[view]
+ semID := w.semIDFor(virtID)
+ sem, found := w.callbacks.LookupSemantic(semID)
+ if found {
+ off := f32.Pt(float32(screenX), float32(screenY))
+ if err := w.initAccessibilityNodeInfo(env, sem, off, info); err != nil {
+ panic(err)
+ }
+ }
+ return info
+}
+
+//export Java_org_gioui_GioView_onTouchExploration
+func Java_org_gioui_GioView_onTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong, x, y C.jfloat) {
+ w := views[view]
+ semID, _ := w.callbacks.SemanticAt(f32.Pt(float32(x), float32(y)))
+ if w.semantic.hoverID == semID {
+ return
+ }
+ // Android expects ENTER before EXIT.
+ if semID != 0 {
+ callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_ENTER, jvalue(w.virtualIDFor(semID)))
+ }
+ if prevID := w.semantic.hoverID; prevID != 0 {
+ callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(prevID)))
+ }
+ w.semantic.hoverID = semID
+}
+
+//export Java_org_gioui_GioView_onExitTouchExploration
+func Java_org_gioui_GioView_onExitTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong) {
+ w := views[view]
+ if w.semantic.hoverID != 0 {
+ callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(w.semantic.hoverID)))
+ w.semantic.hoverID = 0
+ }
+}
+
+//export Java_org_gioui_GioView_onA11yFocus
+func Java_org_gioui_GioView_onA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
+ w := views[view]
+ if semID := w.semIDFor(virtID); semID != w.semantic.focusID {
+ w.semantic.focusID = semID
+ // Android needs invalidate to refresh the TalkBack focus indicator.
+ callVoidMethod(env, w.view, gioView.invalidate)
+ }
+}
+
+//export Java_org_gioui_GioView_onClearA11yFocus
+func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) {
+ w := views[view]
+ if w.semantic.focusID == w.semIDFor(virtID) {
+ w.semantic.focusID = 0
+ }
+}
+
+func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNode, off f32.Point, info C.jobject) error {
+ for _, ch := range sem.Children {
+ err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
+ if err != nil {
+ return err
+ }
+ }
+ if sem.ParentID != 0 {
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setParent, jvalue(w.view), jvalue(w.virtualIDFor(sem.ParentID))); err != nil {
+ return err
+ }
+ b := sem.Desc.Bounds.Add(off)
+ rect, err := newObject(env, android.rect.cls, android.rect.cons,
+ jvalue(b.Min.X),
+ jvalue(b.Min.Y),
+ jvalue(b.Max.X),
+ jvalue(b.Max.Y),
+ )
+ if err != nil {
+ return err
+ }
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setBoundsInScreen, jvalue(rect)); err != nil {
+ return err
+ }
+ }
+ d := sem.Desc
+ if l := d.Label; l != "" {
+ jlbl := javaString(env, l)
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setText, jvalue(jlbl)); err != nil {
+ return err
+ }
+ }
+ if d.Description != "" {
+ jd := javaString(env, d.Description)
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setContentDescription, jvalue(jd)); err != nil {
+ return err
+ }
+ }
+ addAction := func(act C.jint) {
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.addAction, jvalue(act)); err != nil {
+ panic(err)
+ }
+ }
+ if d.Gestures&router.ClickGesture != 0 {
+ addAction(ACTION_CLICK)
+ }
+ clsName := android.strings.androidViewView
+ selectMethod := android.accessibilityNodeInfo.setChecked
+ checkable := false
+ switch d.Class {
+ case semantic.Button:
+ clsName = android.strings.androidWidgetButton
+ case semantic.CheckBox:
+ checkable = true
+ clsName = android.strings.androidWidgetCheckBox
+ case semantic.Editor:
+ clsName = android.strings.androidWidgetEditText
+ case semantic.RadioButton:
+ selectMethod = android.accessibilityNodeInfo.setSelected
+ clsName = android.strings.androidWidgetRadioButton
+ case semantic.Switch:
+ checkable = true
+ clsName = android.strings.androidWidgetSwitch
+ }
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setClassName, jvalue(clsName)); err != nil {
+ panic(err)
+ }
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setCheckable, jvalue(javaBool(checkable))); err != nil {
+ panic(err)
+ }
+ if err := callVoidMethod(env, info, selectMethod, jvalue(javaBool(d.Selected))); err != nil {
+ panic(err)
+ }
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setEnabled, jvalue(javaBool(!d.Disabled))); err != nil {
+ panic(err)
+ }
+ isFocus := w.semantic.focusID == sem.ID
+ if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setAccessibilityFocused, jvalue(javaBool(isFocus))); err != nil {
+ panic(err)
+ }
+ if isFocus {
+ addAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)
+ } else {
+ addAction(ACTION_ACCESSIBILITY_FOCUS)
+ }
+ return nil
+}
+
+func (w *window) virtualIDFor(id router.SemanticID) C.jint {
+ // TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64.
+ if id == w.semantic.rootID {
+ return HOST_VIEW_ID
+ }
+ return C.jint(id)
+}
+
+func (w *window) semIDFor(virtID C.jint) router.SemanticID {
+ if virtID == HOST_VIEW_ID {
+ return w.semantic.rootID
+ }
+ return router.SemanticID(virtID)
+}
+
+func (w *window) detach(env *C.JNIEnv) {
+ callVoidMethod(env, w.view, gioView.unregister)
+ w.callbacks.Event(ViewEvent{})
+ w.callbacks.SetDriver(nil)
+ delete(views, C.jlong(w.view))
+ C.jni_DeleteGlobalRef(env, w.view)
+ w.view = 0
+}
+
+func (w *window) setVisible(env *C.JNIEnv) {
+ width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
+ if width == 0 || height == 0 {
+ return
+ }
+ w.setStage(system.StageRunning)
+ w.draw(env, true)
+}
+
+func (w *window) setStage(stage system.Stage) {
+ if stage == w.stage {
+ return
+ }
+ w.stage = stage
+ w.callbacks.Event(system.StageEvent{stage})
+}
+
+func (w *window) setVisual(visID int) error {
+ if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
+ return errors.New("ANativeWindow_setBuffersGeometry failed")
+ }
+ return nil
+}
+
+func (w *window) nativeWindow() (*C.ANativeWindow, int, int) {
+ width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
+ return w.win, int(width), int(height)
+}
+
+func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
+ dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity))
+ w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale))
+ switch dpi {
+ case C.ACONFIGURATION_DENSITY_NONE,
+ C.ACONFIGURATION_DENSITY_DEFAULT,
+ C.ACONFIGURATION_DENSITY_ANY:
+ // Assume standard density.
+ w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
+ default:
+ w.dpi = int(dpi)
+ }
+}
+
+func (w *window) SetAnimating(anim bool) {
+ w.animating = anim
+ if anim {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ callVoidMethod(env, w.view, gioView.postInvalidate)
+ })
+ }
+}
+
+func (w *window) draw(env *C.JNIEnv, sync bool) {
+ size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
+ if size != w.config.Size {
+ w.config.Size = size
+ w.callbacks.Event(ConfigEvent{Config: w.config})
+ }
+ if size.X == 0 || size.Y == 0 {
+ return
+ }
+ const inchPrDp = 1.0 / 160
+ ppdp := float32(w.dpi) * inchPrDp
+ w.callbacks.Event(frameEvent{
+ FrameEvent: system.FrameEvent{
+ Now: time.Now(),
+ Size: w.config.Size,
+ Insets: w.insets,
+ Metric: unit.Metric{
+ PxPerDp: ppdp,
+ PxPerSp: w.fontScale * ppdp,
+ },
+ },
+ Sync: sync,
+ })
+ a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive)
+ if err != nil {
+ panic(err)
+ }
+ if a11yActive {
+ if newR, oldR := w.callbacks.SemanticRoot(), w.semantic.rootID; newR != oldR {
+ // Remap focus and hover.
+ if oldR == w.semantic.hoverID {
+ w.semantic.hoverID = newR
+ }
+ if oldR == w.semantic.focusID {
+ w.semantic.focusID = newR
+ }
+ w.semantic.rootID = newR
+ callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR)))
+ }
+ w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0])
+ for _, id := range w.semantic.diffs {
+ callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id)))
+ }
+ }
+}
+
+type keyMapper func(devId, keyCode C.int32_t) rune
+
+func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
+ if jvm == nil {
+ panic("nil JVM")
+ }
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ var env *C.JNIEnv
+ if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
+ if res != C.JNI_EDETACHED {
+ panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
+ }
+ if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
+ panic(errors.New("runInJVM: AttachCurrentThread failed"))
+ }
+ defer C.jni_DetachCurrentThread(jvm)
+ }
+
+ f(env)
+}
+
+func convertKeyCode(code C.jint) (string, bool) {
+ var n string
+ switch code {
+ case C.AKEYCODE_DPAD_UP:
+ n = key.NameUpArrow
+ case C.AKEYCODE_DPAD_DOWN:
+ n = key.NameDownArrow
+ case C.AKEYCODE_DPAD_LEFT:
+ n = key.NameLeftArrow
+ case C.AKEYCODE_DPAD_RIGHT:
+ n = key.NameRightArrow
+ case C.AKEYCODE_FORWARD_DEL:
+ n = key.NameDeleteForward
+ case C.AKEYCODE_DEL:
+ n = key.NameDeleteBackward
+ case C.AKEYCODE_NUMPAD_ENTER:
+ n = key.NameEnter
+ case C.AKEYCODE_ENTER:
+ n = key.NameEnter
+ default:
+ return "", false
+ }
+ return n, true
+}
+
+//export Java_org_gioui_GioView_onKeyEvent
+func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
+ w := views[handle]
+ if n, ok := convertKeyCode(keyCode); ok {
+ w.callbacks.Event(key.Event{Name: n})
+ }
+ if r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
+ w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
+ }
+}
+
+//export Java_org_gioui_GioView_onTouchEvent
+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) {
+ w := views[handle]
+ var typ pointer.Type
+ switch action {
+ case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
+ typ = pointer.Press
+ case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
+ typ = pointer.Release
+ case C.AMOTION_EVENT_ACTION_CANCEL:
+ typ = pointer.Cancel
+ case C.AMOTION_EVENT_ACTION_MOVE:
+ typ = pointer.Move
+ case C.AMOTION_EVENT_ACTION_SCROLL:
+ typ = pointer.Scroll
+ default:
+ return
+ }
+ var src pointer.Source
+ var btns pointer.Buttons
+ if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
+ btns |= pointer.ButtonPrimary
+ }
+ if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
+ btns |= pointer.ButtonSecondary
+ }
+ if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
+ btns |= pointer.ButtonTertiary
+ }
+ switch tool {
+ case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
+ src = pointer.Touch
+ case C.AMOTION_EVENT_TOOL_TYPE_STYLUS:
+ src = pointer.Touch
+ case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
+ src = pointer.Mouse
+ case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
+ // For example, triggered via 'adb shell input tap'.
+ // Instead of discarding it, treat it as a touch event.
+ src = pointer.Touch
+ default:
+ return
+ }
+ w.callbacks.Event(pointer.Event{
+ Type: typ,
+ Source: src,
+ Buttons: btns,
+ PointerID: pointer.ID(pointerID),
+ Time: time.Duration(t) * time.Millisecond,
+ Position: f32.Point{X: float32(x), Y: float32(y)},
+ Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
+ })
+}
+
+func (w *window) ShowTextInput(show bool) {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ if show {
+ callVoidMethod(env, w.view, gioView.showTextInput)
+ } else {
+ callVoidMethod(env, w.view, gioView.hideTextInput)
+ }
+ })
+}
+
+func (w *window) SetInputHint(mode key.InputHint) {
+ // Constants defined at https://developer.android.com/reference/android/text/InputType.
+ const (
+ TYPE_NULL = 0
+ TYPE_CLASS_NUMBER = 2
+ TYPE_NUMBER_FLAG_DECIMAL = 8192
+ TYPE_NUMBER_FLAG_SIGNED = 4096
+ TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288
+ TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 144
+ )
+
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ var m jvalue
+ switch mode {
+ case key.HintNumeric:
+ m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED
+ default:
+ // TYPE_NULL, since TYPE_CLASS_TEXT isn't currently supported.
+ m = TYPE_NULL
+ }
+
+ // The TYPE_TEXT_FLAG_NO_SUGGESTIONS and TYPE_TEXT_VARIATION_VISIBLE_PASSWORD are used to fix the
+ // Samsung keyboard compatibility, forcing to disable the suggests/auto-complete. gio#116.
+ m = m | TYPE_TEXT_FLAG_NO_SUGGESTIONS | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+
+ callVoidMethod(env, w.view, gioView.setInputHint, m)
+ })
+}
+
+func javaBool(b bool) C.jboolean {
+ if b {
+ return C.JNI_TRUE
+ } else {
+ return C.JNI_FALSE
+ }
+}
+
+func javaString(env *C.JNIEnv, str string) C.jstring {
+ if str == "" {
+ return 0
+ }
+ utf16Chars := utf16.Encode([]rune(str))
+ return C.jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
+}
+
+func varArgs(args []jvalue) *C.jvalue {
+ if len(args) == 0 {
+ return nil
+ }
+ return (*C.jvalue)(unsafe.Pointer(&args[0]))
+}
+
+func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
+ C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
+ return exception(env)
+}
+
+func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+ res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
+ return res, exception(env)
+}
+
+func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
+ C.jni_CallVoidMethodA(env, obj, method, varArgs(args))
+ return exception(env)
+}
+
+func callBooleanMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (bool, error) {
+ res := C.jni_CallBooleanMethodA(env, obj, method, varArgs(args))
+ return res == C.JNI_TRUE, exception(env)
+}
+
+func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+ res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args))
+ return res, exception(env)
+}
+
+func newObject(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
+ res := C.jni_NewObjectA(env, cls, method, varArgs(args))
+ return res, exception(env)
+}
+
+// exception returns an error corresponding to the pending
+// exception, or nil if no exception is pending. The pending
+// exception is cleared.
+func exception(env *C.JNIEnv) error {
+ thr := C.jni_ExceptionOccurred(env)
+ if thr == 0 {
+ return nil
+ }
+ C.jni_ExceptionClear(env)
+ cls := getObjectClass(env, C.jobject(thr))
+ toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
+ msg, err := callObjectMethod(env, C.jobject(thr), toString)
+ if err != nil {
+ return err
+ }
+ return errors.New(goString(env, C.jstring(msg)))
+}
+
+func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
+ if obj == 0 {
+ panic("null object")
+ }
+ cls := C.jni_GetObjectClass(env, C.jobject(obj))
+ if err := exception(env); err != nil {
+ // GetObjectClass should never fail.
+ panic(err)
+ }
+ return cls
+}
+
+// goString converts the JVM jstring to a Go string.
+func goString(env *C.JNIEnv, str C.jstring) string {
+ if str == 0 {
+ return ""
+ }
+ strlen := C.jni_GetStringLength(env, C.jstring(str))
+ chars := C.jni_GetStringChars(env, C.jstring(str))
+ var utf16Chars []uint16
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
+ hdr.Data = uintptr(unsafe.Pointer(chars))
+ hdr.Cap = int(strlen)
+ hdr.Len = int(strlen)
+ utf8 := utf16.Decode(utf16Chars)
+ return string(utf8)
+}
+
+func findClass(env *C.JNIEnv, name string) C.jclass {
+ cn := C.CString(name)
+ defer C.free(unsafe.Pointer(cn))
+ return C.jni_FindClass(env, cn)
+}
+
+func osMain() {
+}
+
+func newWindow(window *callbacks, options []Option) error {
+ mainWindow.in <- windowAndConfig{window, options}
+ return <-mainWindow.errs
+}
+
+func (w *window) WriteClipboard(s string) {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ jstr := javaString(env, s)
+ callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
+ jvalue(android.appCtx), jvalue(jstr))
+ })
+}
+
+func (w *window) ReadClipboard() {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
+ jvalue(android.appCtx))
+ if err != nil {
+ return
+ }
+ content := goString(env, C.jstring(c))
+ w.callbacks.Event(clipboard.Event{Text: content})
+ })
+}
+
+func (w *window) Configure(options []Option) {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ prev := w.config
+ cnf := w.config
+ cnf.apply(unit.Metric{}, options)
+ if prev.Orientation != cnf.Orientation {
+ w.config.Orientation = cnf.Orientation
+ setOrientation(env, w.view, cnf.Orientation)
+ }
+ if prev.NavigationColor != cnf.NavigationColor {
+ w.config.NavigationColor = cnf.NavigationColor
+ setNavigationColor(env, w.view, cnf.NavigationColor)
+ }
+ if prev.StatusColor != cnf.StatusColor {
+ w.config.StatusColor = cnf.StatusColor
+ setStatusColor(env, w.view, cnf.StatusColor)
+ }
+ if prev.Mode != cnf.Mode {
+ switch cnf.Mode {
+ case Fullscreen:
+ callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
+ w.config.Mode = Fullscreen
+ case Windowed:
+ callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
+ w.config.Mode = Windowed
+ }
+ }
+ if w.config != prev {
+ w.callbacks.Event(ConfigEvent{Config: w.config})
+ }
+ })
+}
+
+func (w *window) Raise() {}
+
+func (w *window) SetCursor(name pointer.CursorName) {
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ setCursor(env, w.view, name)
+ })
+}
+
+func (w *window) Wakeup() {
+ runOnMain(func(env *C.JNIEnv) {
+ w.callbacks.Event(wakeupEvent{})
+ })
+}
+
+func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
+ var curID int
+ switch name {
+ default:
+ fallthrough
+ case pointer.CursorDefault:
+ curID = 1000 // TYPE_ARROW
+ case pointer.CursorText:
+ curID = 1008 // TYPE_TEXT
+ case pointer.CursorPointer:
+ curID = 1002 // TYPE_HAND
+ case pointer.CursorCrossHair:
+ curID = 1007 // TYPE_CROSSHAIR
+ case pointer.CursorColResize:
+ curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
+ case pointer.CursorRowResize:
+ curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
+ case pointer.CursorNone:
+ curID = 0 // TYPE_NULL
+ }
+ callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
+}
+
+func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
+ var (
+ id int
+ idFallback int // Used only for SDK 17 or older.
+ )
+ // Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
+ switch mode {
+ case AnyOrientation:
+ id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
+ case LandscapeOrientation:
+ id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
+ case PortraitOrientation:
+ id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
+ }
+ callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
+}
+
+func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
+ callVoidMethod(env, view, gioView.setStatusColor,
+ jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
+ jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
+ )
+}
+
+func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
+ callVoidMethod(env, view, gioView.setNavigationColor,
+ jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)),
+ jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)),
+ )
+}
+
+// Close the window. Not implemented for Android.
+func (w *window) Close() {}
+
+// Maximize maximizes the window. Not implemented for Android.
+func (w *window) Maximize() {}
+
+// Center the window. Not implemented for Android.
+func (w *window) Center() {}
+
+// runOnMain runs a function on the Java main thread.
+func runOnMain(f func(env *C.JNIEnv)) {
+ go func() {
+ mainFuncs <- f
+ runInJVM(javaVM(), func(env *C.JNIEnv) {
+ callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
+ })
+ }()
+}
+
+//export Java_org_gioui_Gio_scheduleMainFuncs
+func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
+ for {
+ select {
+ case f := <-mainFuncs:
+ f(env)
+ default:
+ return
+ }
+ }
+}
+
+func (_ ViewEvent) ImplementsEvent() {}