//go:build android package main /* #cgo CFLAGS: -Werror #cgo LDFLAGS: -landroid #include #include static jint jni_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { return JNI_GetCreatedJavaVMs(vmBuf, bufLen, nVMs); } 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 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 jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { return (*env)->CallObjectMethodA(env, obj, method, args); } static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) { (*env)->CallStaticVoidMethodA(env, cls, methodID, args); } static jvalue jni_ValueObject(jobject obj) { jvalue value; value.l = obj; return value; } static jthrowable jni_ExceptionOccurred(JNIEnv *env) { return (*env)->ExceptionOccurred(env); } static void jni_ExceptionClear(JNIEnv *env) { (*env)->ExceptionClear(env); } static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) { return (*env)->NewString(env, unicodeChars, len); } static int jni_IsNull(jobject obj) { return obj == NULL; } */ import "C" import ( "fmt" "strings" "unicode/utf16" "unsafe" "gioui.org/app" ) type androidVaultSharer struct{} func newPlatformVaultSharer(goos string) vaultSharer { return androidVaultSharer{} } func (androidVaultSharer) ShareVault(path, title string) error { if strings.TrimSpace(path) == "" { return fmt.Errorf("vault path is required") } ctx := C.jobject(unsafe.Pointer(app.AppContext())) if C.jni_IsNull(ctx) != 0 { return fmt.Errorf("android app context is not available") } vm, err := androidJavaVM() if err != nil { return err } var env *C.JNIEnv if res := C.jni_AttachCurrentThread(vm, &env, nil); res != C.JNI_OK { return fmt.Errorf("attach JVM thread: %d", int(res)) } defer C.jni_DetachCurrentThread(vm) sharerClass, err := androidLoadClass(env, ctx, "org.julianfamily.keepassgo.AndroidShare") if err != nil { return err } methodName := cString("shareVault") defer C.free(unsafe.Pointer(methodName)) methodSig := cString("(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V") defer C.free(unsafe.Pointer(methodSig)) method := C.jni_GetStaticMethodID(env, sharerClass, methodName, methodSig) if method == nil { if err := androidJNIError(env, "resolve shareVault method"); err != nil { return err } return fmt.Errorf("resolve shareVault method") } jPath := androidJavaString(env, path) jTitle := androidJavaString(env, title) args := [3]C.jvalue{} args[0] = C.jni_ValueObject(ctx) args[1] = C.jni_ValueObject(C.jobject(jPath)) args[2] = C.jni_ValueObject(C.jobject(jTitle)) C.jni_CallStaticVoidMethodA(env, sharerClass, method, &args[0]) if err := androidJNIError(env, "share vault"); err != nil { return err } return nil } func androidJavaVM() (*C.JavaVM, error) { var vms [1]*C.JavaVM var count C.jsize if res := C.jni_GetCreatedJavaVMs(&vms[0], 1, &count); res != C.JNI_OK { return nil, fmt.Errorf("get created Java VMs: %d", int(res)) } if count == 0 || vms[0] == nil { return nil, fmt.Errorf("no Java VM available") } return vms[0], nil } func androidLoadClass(env *C.JNIEnv, ctx C.jobject, name string) (C.jclass, error) { var zeroClass C.jclass contextClass := C.jni_GetObjectClass(env, ctx) getClassLoaderName := cString("getClassLoader") defer C.free(unsafe.Pointer(getClassLoaderName)) getClassLoaderSig := cString("()Ljava/lang/ClassLoader;") defer C.free(unsafe.Pointer(getClassLoaderSig)) getClassLoader := C.jni_GetMethodID(env, contextClass, getClassLoaderName, getClassLoaderSig) if getClassLoader == nil { if err := androidJNIError(env, "resolve getClassLoader"); err != nil { return zeroClass, err } return zeroClass, fmt.Errorf("resolve getClassLoader") } classLoader := C.jni_CallObjectMethodA(env, ctx, getClassLoader, nil) if err := androidJNIError(env, "load class loader"); err != nil { return zeroClass, err } if C.jni_IsNull(classLoader) != 0 { return zeroClass, fmt.Errorf("android class loader is nil") } classLoaderClass := C.jni_GetObjectClass(env, classLoader) loadClassName := cString("loadClass") defer C.free(unsafe.Pointer(loadClassName)) loadClassSig := cString("(Ljava/lang/String;)Ljava/lang/Class;") defer C.free(unsafe.Pointer(loadClassSig)) loadClass := C.jni_GetMethodID(env, classLoaderClass, loadClassName, loadClassSig) if loadClass == nil { if err := androidJNIError(env, "resolve loadClass"); err != nil { return zeroClass, err } return zeroClass, fmt.Errorf("resolve loadClass") } jClassName := androidJavaString(env, name) args := [1]C.jvalue{} args[0] = C.jni_ValueObject(C.jobject(jClassName)) loaded := C.jni_CallObjectMethodA(env, classLoader, loadClass, &args[0]) if err := androidJNIError(env, "load AndroidShare class"); err != nil { return zeroClass, err } if C.jni_IsNull(loaded) != 0 { return zeroClass, fmt.Errorf("load AndroidShare class returned nil") } return C.jclass(loaded), nil } func androidJNIError(env *C.JNIEnv, action string) error { if thr := C.jni_ExceptionOccurred(env); C.jni_IsNull(C.jobject(thr)) == 0 { C.jni_ExceptionClear(env) return fmt.Errorf("%s: Java exception", action) } return nil } func androidJavaString(env *C.JNIEnv, s string) C.jstring { chars := utf16.Encode([]rune(s)) if len(chars) == 0 { return C.jni_NewString(env, nil, 0) } return C.jni_NewString(env, (*C.jchar)(unsafe.Pointer(unsafe.SliceData(chars))), C.jsize(len(chars))) } func cString(value string) *C.char { return C.CString(value) }