//go:build android package main /* #cgo CFLAGS: -Werror #cgo LDFLAGS: -landroid #include #include 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" _ "unsafe" ) type androidVaultSharer struct{} //go:linkname gioJavaVM gioui.org/app.javaVM func gioJavaVM() *C.JavaVM //go:linkname gioRunInJVM gioui.org/app.runInJVM func gioRunInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) 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") } var callErr error gioRunInJVM(gioJavaVM(), func(env *C.JNIEnv) { sharerClass, err := androidLoadClass(env, ctx, "org.julianfamily.keepassgo.AndroidShare") if err != nil { callErr = err return } 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 { callErr = androidJNIError(env, "resolve shareVault method") if callErr == nil { callErr = fmt.Errorf("resolve shareVault method") } return } 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]) callErr = androidJNIError(env, "share vault") }) return callErr } 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) }