Use Gio JVM bridge for Android sharing
This commit is contained in:
+35
-59
@@ -9,18 +9,6 @@ package main
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -72,10 +60,17 @@ import (
|
||||
"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{}
|
||||
}
|
||||
@@ -88,55 +83,36 @@ func (androidVaultSharer) ShareVault(path, title string) error {
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
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) {
|
||||
|
||||
+28
-4
@@ -444,7 +444,12 @@ func TestUIAPITokenLifecycleManagement(t *testing.T) {
|
||||
func TestUIAPITokenPolicyRulesCanBeAddedAndRemoved(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||
DefaultSaveAsPath: filepath.Join(t.TempDir(), "vault.kdbx"),
|
||||
RecentVaultsPath: filepath.Join(t.TempDir(), "recent-vaults.json"),
|
||||
RecentRemotesPath: filepath.Join(t.TempDir(), "recent-remotes.json"),
|
||||
UIPreferencesPath: filepath.Join(t.TempDir(), "ui-prefs.json"),
|
||||
})
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
if err := u.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
@@ -582,7 +587,12 @@ func TestPolicyRulePartsFormatsGroupAndEntryResources(t *testing.T) {
|
||||
func TestUIAPITokenDetailPanelHandlesMissingRemoveClickables(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||
DefaultSaveAsPath: filepath.Join(t.TempDir(), "vault.kdbx"),
|
||||
RecentVaultsPath: filepath.Join(t.TempDir(), "recent-vaults.json"),
|
||||
RecentRemotesPath: filepath.Join(t.TempDir(), "recent-remotes.json"),
|
||||
UIPreferencesPath: filepath.Join(t.TempDir(), "ui-prefs.json"),
|
||||
})
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
if err := u.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
@@ -1615,10 +1625,17 @@ func TestUIOpenRemoteAndSaveThroughConfiguredWebDAVTarget(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||
DefaultSaveAsPath: filepath.Join(t.TempDir(), "vault.kdbx"),
|
||||
RecentVaultsPath: filepath.Join(t.TempDir(), "recent-vaults.json"),
|
||||
RecentRemotesPath: filepath.Join(t.TempDir(), "recent-remotes.json"),
|
||||
UIPreferencesPath: filepath.Join(t.TempDir(), "ui-prefs.json"),
|
||||
})
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
u.remoteBaseURL.SetText(server.URL)
|
||||
u.remotePath.SetText("vaults/main.kdbx")
|
||||
u.selectedVaultRemoteProfileID = ""
|
||||
u.selectedVaultRemoteCredentialEntryID = ""
|
||||
|
||||
if err := u.openRemoteAction(); err != nil {
|
||||
t.Fatalf("openRemoteAction() error = %v", err)
|
||||
@@ -2059,10 +2076,17 @@ func TestUIRemoteSaveConflictShowsVisibleErrorAndKeepsDirtyState(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
u := newUIWithSession("desktop", &session.Manager{})
|
||||
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||
DefaultSaveAsPath: filepath.Join(t.TempDir(), "vault.kdbx"),
|
||||
RecentVaultsPath: filepath.Join(t.TempDir(), "recent-vaults.json"),
|
||||
RecentRemotesPath: filepath.Join(t.TempDir(), "recent-remotes.json"),
|
||||
UIPreferencesPath: filepath.Join(t.TempDir(), "ui-prefs.json"),
|
||||
})
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
u.remoteBaseURL.SetText(server.URL)
|
||||
u.remotePath.SetText("vaults/main.kdbx")
|
||||
u.selectedVaultRemoteProfileID = ""
|
||||
u.selectedVaultRemoteCredentialEntryID = ""
|
||||
|
||||
if err := u.openRemoteAction(); err != nil {
|
||||
t.Fatalf("openRemoteAction() error = %v", err)
|
||||
|
||||
Reference in New Issue
Block a user