185 lines
5.4 KiB
Go
185 lines
5.4 KiB
Go
//go:build android
|
|
|
|
package keepassgo
|
|
|
|
/*
|
|
#cgo CFLAGS: -Werror
|
|
#cgo LDFLAGS: -landroid
|
|
|
|
#include <jni.h>
|
|
#include <stdlib.h>
|
|
|
|
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)
|
|
}
|