//go:build !darwin || !linux // +build !darwin !linux package main /* #cgo LDFLAGS: -landroid -llog #include #include "jni_android.h" */ import "C" import ( "errors" "fmt" "runtime" "unsafe" "gioui.org/app" _ "gioui.org/app/permission/storage" "gioui.org/io/event" "pad/internal/editor" ) type JNIEnv = C.JNIEnv var ( startpath = "/storage/emulated/0/Notes" jvm uintptr theJVM *C.JavaVM ) // activeLogic is set in main before the window loop: the onStop JNI hook // (pad_flush_session, below) needs a handle to the logic it serves. var activeLogic *editor.Logic func impl_start() {} func handleEvent(e event.Event) { switch e := e.(type) { case app.AndroidViewEvent: // View == 0 is Gio's DETACH signal (window.detach sends an empty // AndroidViewEvent when the GioView is destroyed — e.g. the activity // going away on a recents-wipe). There is nothing to register for a // detached view; passing the null ref on would abort in JNI // (GetObjectClass on null). A re-attach arrives as a fresh event // with a live view. if e.View == 0 { return } theJVM = (*C.JavaVM)(unsafe.Pointer(app.JavaVM())) RunInJVM(func(env *JNIEnv) { C.registerFragment(env, (C.jobject)(unsafe.Pointer(e.View))) }) } } func RunInJVM(f func(env *C.JNIEnv)) { runtime.LockOSThread() defer runtime.UnlockOSThread() var env *C.JNIEnv var detach bool if res := C.GetEnv(theJVM, &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.AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK { panic(errors.New("runInJVM: AttachCurrentThread failed")) } detach = true } if detach { defer func() { C.DetachCurrentThread(theJVM) }() } f(env) } // SetGestureExclusions forwards the selection-handle grab boxes (view-local // px) to View.setSystemGestureExclusionRects on Android so drags starting on // an edge handle are not stolen by the system back gesture (see // jni_android.c). It is called from the frame loop, which runs on the Android // UI thread. No-op (C-side) before API 29. // pad_flush_session is the cgo export behind GioActivity.onStop (the // build script patches onStop into the smali and declares the matching // static native method). The OS gives no user-space hook for the process // kill that follows a recents-wipe, so onStop is the last reliable moment // to persist the session snapshot. // //export pad_flush_session func pad_flush_session() { if activeLogic != nil { activeLogic.FlushSession() } } func SetGestureExclusions(rects [][4]int) { if theJVM == nil { return } RunInJVM(func(env *C.JNIEnv) { if len(rects) == 0 { C.SetGestureExclusions(env, 0, nil) return } buf := make([]C.jint, len(rects)*4) for i, rc := range rects { for j, v := range rc { buf[i*4+j] = C.jint(v) } } C.SetGestureExclusions(env, C.jint(len(rects)), &buf[0]) }) } // sessionFilePath is where the relaunch session file (spec §7) lives. The // primary storage dir already carries the app's caches (.pad browser index // dirs, PadPerf), and the app holds the storage permission. func sessionFilePath() string { return "/storage/emulated/0/Pad/session.json" } func OpenFile(path string) { var env *C.JNIEnv var detach bool if res := C.GetEnv(theJVM, &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.AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK { panic(errors.New("OpenFile: AttachCurrentThread failed")) } detach = true } if detach { defer func() { C.DetachCurrentThread(theJVM) }() } cpath := C.CString(path) C.open_file_in_termux(env, cpath) C.free(unsafe.Pointer(cpath)) }