gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/os_android.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     4  
     5  /*
     6  #cgo LDFLAGS: -landroid
     7  
     8  #include <android/native_window_jni.h>
     9  #include <android/configuration.h>
    10  #include <android/keycodes.h>
    11  #include <android/input.h>
    12  #include <stdlib.h>
    13  #include "os_android.h"
    14  */
    15  import "C"
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"image"
    21  	"runtime"
    22  	"runtime/debug"
    23  	"sync"
    24  	"time"
    25  	"unsafe"
    26  
    27  	"gioui.org/ui"
    28  	"gioui.org/ui/f32"
    29  	"gioui.org/ui/key"
    30  	"gioui.org/ui/pointer"
    31  )
    32  
    33  type window struct {
    34  	*Window
    35  
    36  	view C.jobject
    37  
    38  	dpi       int
    39  	fontScale float32
    40  	insets    Insets
    41  
    42  	stage   Stage
    43  	started bool
    44  
    45  	mu        sync.Mutex
    46  	win       *C.ANativeWindow
    47  	animating bool
    48  
    49  	mgetDensity                    C.jmethodID
    50  	mgetFontScale                  C.jmethodID
    51  	mshowTextInput                 C.jmethodID
    52  	mhideTextInput                 C.jmethodID
    53  	mpostFrameCallback             C.jmethodID
    54  	mpostFrameCallbackOnMainThread C.jmethodID
    55  }
    56  
    57  var theJVM *C.JavaVM
    58  
    59  var views = make(map[C.jlong]*window)
    60  
    61  var mainWindow = newWindowRendezvous()
    62  
    63  func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
    64  	m := C.CString(method)
    65  	defer C.free(unsafe.Pointer(m))
    66  	s := C.CString(sig)
    67  	defer C.free(unsafe.Pointer(s))
    68  	return C.gio_jni_GetMethodID(env, class, m, s)
    69  }
    70  
    71  func jniGetStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
    72  	m := C.CString(method)
    73  	defer C.free(unsafe.Pointer(m))
    74  	s := C.CString(sig)
    75  	defer C.free(unsafe.Pointer(s))
    76  	return C.gio_jni_GetStaticMethodID(env, class, m, s)
    77  }
    78  
    79  //export runGoMain
    80  func runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray) {
    81  	dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
    82  	if dirBytes == nil {
    83  		panic("runGoMain: GetByteArrayElements failed")
    84  	}
    85  	n := C.gio_jni_GetArrayLength(env, jdataDir)
    86  	dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
    87  	setDataDir(dataDir)
    88  	C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
    89  	runMain()
    90  }
    91  
    92  //export setJVM
    93  func setJVM(vm *C.JavaVM) {
    94  	theJVM = vm
    95  }
    96  
    97  //export onCreateView
    98  func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
    99  	view = C.gio_jni_NewGlobalRef(env, view)
   100  	w := &window{
   101  		view:                           view,
   102  		mgetDensity:                    jniGetMethodID(env, class, "getDensity", "()I"),
   103  		mgetFontScale:                  jniGetMethodID(env, class, "getFontScale", "()F"),
   104  		mshowTextInput:                 jniGetMethodID(env, class, "showTextInput", "()V"),
   105  		mhideTextInput:                 jniGetMethodID(env, class, "hideTextInput", "()V"),
   106  		mpostFrameCallback:             jniGetMethodID(env, class, "postFrameCallback", "()V"),
   107  		mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
   108  	}
   109  	wopts := <-mainWindow.out
   110  	w.Window = wopts.window
   111  	w.Window.setDriver(w)
   112  	handle := C.jlong(view)
   113  	views[handle] = w
   114  	w.loadConfig(env, class)
   115  	w.setStage(StagePaused)
   116  	return handle
   117  }
   118  
   119  //export onDestroyView
   120  func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   121  	w := views[handle]
   122  	w.setDriver(nil)
   123  	delete(views, handle)
   124  	C.gio_jni_DeleteGlobalRef(env, w.view)
   125  	w.view = 0
   126  }
   127  
   128  //export onStopView
   129  func onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   130  	w := views[handle]
   131  	w.started = false
   132  	w.setStage(StagePaused)
   133  }
   134  
   135  //export onStartView
   136  func onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   137  	w := views[handle]
   138  	w.started = true
   139  	if w.aNativeWindow() != nil {
   140  		w.setVisible()
   141  	}
   142  }
   143  
   144  //export onSurfaceDestroyed
   145  func onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
   146  	w := views[handle]
   147  	w.mu.Lock()
   148  	w.win = nil
   149  	w.mu.Unlock()
   150  	w.setStage(StagePaused)
   151  }
   152  
   153  //export onSurfaceChanged
   154  func onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
   155  	w := views[handle]
   156  	w.mu.Lock()
   157  	w.win = C.ANativeWindow_fromSurface(env, surf)
   158  	w.mu.Unlock()
   159  	if w.started {
   160  		w.setVisible()
   161  	}
   162  }
   163  
   164  //export onLowMemory
   165  func onLowMemory() {
   166  	runtime.GC()
   167  	debug.FreeOSMemory()
   168  }
   169  
   170  //export onConfigurationChanged
   171  func onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
   172  	w := views[view]
   173  	w.loadConfig(env, class)
   174  	if w.stage >= StageRunning {
   175  		w.draw(true)
   176  	}
   177  }
   178  
   179  //export onFrameCallback
   180  func onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
   181  	w, exist := views[view]
   182  	if !exist {
   183  		return
   184  	}
   185  	if w.stage < StageRunning {
   186  		return
   187  	}
   188  	w.mu.Lock()
   189  	anim := w.animating
   190  	w.mu.Unlock()
   191  	if anim {
   192  		runInJVM(func(env *C.JNIEnv) {
   193  			C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallback)
   194  		})
   195  		w.draw(false)
   196  	}
   197  }
   198  
   199  //export onBack
   200  func onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
   201  	w := views[view]
   202  	ev := &CommandEvent{Type: CommandBack}
   203  	w.event(ev)
   204  	if ev.Cancel {
   205  		return C.JNI_TRUE
   206  	}
   207  	return C.JNI_FALSE
   208  }
   209  
   210  //export onFocusChange
   211  func onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
   212  	w := views[view]
   213  	w.event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
   214  }
   215  
   216  //export onWindowInsets
   217  func onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
   218  	w := views[view]
   219  	w.insets = Insets{
   220  		Top:    ui.Px(float32(top)),
   221  		Right:  ui.Px(float32(right)),
   222  		Bottom: ui.Px(float32(bottom)),
   223  		Left:   ui.Px(float32(left)),
   224  	}
   225  	if w.stage >= StageRunning {
   226  		w.draw(true)
   227  	}
   228  }
   229  
   230  func (w *window) setVisible() {
   231  	win := w.aNativeWindow()
   232  	width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
   233  	if width == 0 || height == 0 {
   234  		return
   235  	}
   236  	w.setStage(StageRunning)
   237  	w.draw(true)
   238  }
   239  
   240  func (w *window) setStage(stage Stage) {
   241  	if stage == w.stage {
   242  		return
   243  	}
   244  	w.stage = stage
   245  	w.event(StageEvent{stage})
   246  }
   247  
   248  func (w *window) display() unsafe.Pointer {
   249  	return nil
   250  }
   251  
   252  func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
   253  	win := w.aNativeWindow()
   254  	var width, height int
   255  	if win != nil {
   256  		if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
   257  			panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
   258  		}
   259  		w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
   260  		width, height = int(w), int(h)
   261  	}
   262  	return unsafe.Pointer(win), width, height
   263  }
   264  
   265  func (w *window) aNativeWindow() *C.ANativeWindow {
   266  	w.mu.Lock()
   267  	defer w.mu.Unlock()
   268  	return w.win
   269  }
   270  
   271  func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
   272  	dpi := int(C.gio_jni_CallIntMethod(env, w.view, w.mgetDensity))
   273  	w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, w.mgetFontScale))
   274  	switch dpi {
   275  	case C.ACONFIGURATION_DENSITY_NONE,
   276  		C.ACONFIGURATION_DENSITY_DEFAULT,
   277  		C.ACONFIGURATION_DENSITY_ANY:
   278  		// Assume standard density.
   279  		w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
   280  	default:
   281  		w.dpi = int(dpi)
   282  	}
   283  }
   284  
   285  func (w *window) setAnimating(anim bool) {
   286  	w.mu.Lock()
   287  	w.animating = anim
   288  	w.mu.Unlock()
   289  	if anim {
   290  		runInJVM(func(env *C.JNIEnv) {
   291  			C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallbackOnMainThread)
   292  		})
   293  	}
   294  }
   295  
   296  func (w *window) draw(sync bool) {
   297  	win := w.aNativeWindow()
   298  	width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
   299  	if width == 0 || height == 0 {
   300  		return
   301  	}
   302  	ppdp := float32(w.dpi) * inchPrDp
   303  	w.event(UpdateEvent{
   304  		Size: image.Point{
   305  			X: int(width),
   306  			Y: int(height),
   307  		},
   308  		Insets: w.insets,
   309  		Config: Config{
   310  			pxPerDp: ppdp,
   311  			pxPerSp: w.fontScale * ppdp,
   312  			now:     time.Now(),
   313  		},
   314  		sync: sync,
   315  	})
   316  }
   317  
   318  type keyMapper func(devId, keyCode C.int32_t) rune
   319  
   320  func runInJVM(f func(env *C.JNIEnv)) {
   321  	runtime.LockOSThread()
   322  	defer runtime.UnlockOSThread()
   323  	var env *C.JNIEnv
   324  	var detach bool
   325  	if res := C.gio_jni_GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
   326  		if res != C.JNI_EDETACHED {
   327  			panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
   328  		}
   329  		if C.gio_jni_AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK {
   330  			panic(errors.New("runInJVM: AttachCurrentThread failed"))
   331  		}
   332  		detach = true
   333  	}
   334  
   335  	if detach {
   336  		defer func() {
   337  			C.gio_jni_DetachCurrentThread(theJVM)
   338  		}()
   339  	}
   340  	f(env)
   341  }
   342  
   343  func convertKeyCode(code C.jint) (rune, bool) {
   344  	var n rune
   345  	switch code {
   346  	case C.AKEYCODE_DPAD_UP:
   347  		n = key.NameUpArrow
   348  	case C.AKEYCODE_DPAD_DOWN:
   349  		n = key.NameDownArrow
   350  	case C.AKEYCODE_DPAD_LEFT:
   351  		n = key.NameLeftArrow
   352  	case C.AKEYCODE_DPAD_RIGHT:
   353  		n = key.NameRightArrow
   354  	case C.AKEYCODE_FORWARD_DEL:
   355  		n = key.NameDeleteForward
   356  	case C.AKEYCODE_DEL:
   357  		n = key.NameDeleteBackward
   358  	default:
   359  		return 0, false
   360  	}
   361  	return n, true
   362  }
   363  
   364  //export onKeyEvent
   365  func onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
   366  	w := views[handle]
   367  	if n, ok := convertKeyCode(keyCode); ok {
   368  		w.event(key.Event{Name: n})
   369  	}
   370  	if r != 0 {
   371  		w.event(key.EditEvent{Text: string(rune(r))})
   372  	}
   373  }
   374  
   375  //export onTouchEvent
   376  func onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y C.jfloat, t C.jlong) {
   377  	w := views[handle]
   378  	var typ pointer.Type
   379  	switch action {
   380  	case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
   381  		typ = pointer.Press
   382  	case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
   383  		typ = pointer.Release
   384  	case C.AMOTION_EVENT_ACTION_CANCEL:
   385  		typ = pointer.Cancel
   386  	case C.AMOTION_EVENT_ACTION_MOVE:
   387  		typ = pointer.Move
   388  	default:
   389  		return
   390  	}
   391  	var src pointer.Source
   392  	switch tool {
   393  	case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
   394  		src = pointer.Touch
   395  	case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
   396  		src = pointer.Mouse
   397  	default:
   398  		return
   399  	}
   400  	w.event(pointer.Event{
   401  		Type:      typ,
   402  		Source:    src,
   403  		PointerID: pointer.ID(pointerID),
   404  		Time:      time.Duration(t) * time.Millisecond,
   405  		Position:  f32.Point{X: float32(x), Y: float32(y)},
   406  	})
   407  }
   408  
   409  func (w *window) showTextInput(show bool) {
   410  	if w.view == 0 {
   411  		return
   412  	}
   413  	runInJVM(func(env *C.JNIEnv) {
   414  		if show {
   415  			C.gio_jni_CallVoidMethod(env, w.view, w.mshowTextInput)
   416  		} else {
   417  			C.gio_jni_CallVoidMethod(env, w.view, w.mhideTextInput)
   418  		}
   419  	})
   420  }
   421  
   422  func main() {
   423  }
   424  
   425  func createWindow(window *Window, opts *windowOptions) error {
   426  	mainWindow.in <- windowAndOptions{window, opts}
   427  	return <-mainWindow.errs
   428  }