Use Gio JVM bridge for Android sharing

This commit is contained in:
Joe Julian
2026-04-06 17:09:28 -07:00
parent d037393a16
commit fe9f3ae112
2 changed files with 63 additions and 63 deletions
+35 -59
View File
@@ -9,18 +9,6 @@ package main
#include <jni.h> #include <jni.h>
#include <stdlib.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) { static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) {
return (*env)->GetObjectClass(env, obj); return (*env)->GetObjectClass(env, obj);
} }
@@ -72,10 +60,17 @@ import (
"unsafe" "unsafe"
"gioui.org/app" "gioui.org/app"
_ "unsafe"
) )
type androidVaultSharer struct{} 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 { func newPlatformVaultSharer(goos string) vaultSharer {
return androidVaultSharer{} return androidVaultSharer{}
} }
@@ -88,55 +83,36 @@ func (androidVaultSharer) ShareVault(path, title string) error {
if C.jni_IsNull(ctx) != 0 { if C.jni_IsNull(ctx) != 0 {
return fmt.Errorf("android app context is not available") return fmt.Errorf("android app context is not available")
} }
vm, err := androidJavaVM() var callErr error
if err != nil { gioRunInJVM(gioJavaVM(), func(env *C.JNIEnv) {
return err sharerClass, err := androidLoadClass(env, ctx, "org.julianfamily.keepassgo.AndroidShare")
} if err != nil {
var env *C.JNIEnv callErr = err
if res := C.jni_AttachCurrentThread(vm, &env, nil); res != C.JNI_OK { return
return fmt.Errorf("attach JVM thread: %d", int(res)) }
} methodName := cString("shareVault")
defer C.jni_DetachCurrentThread(vm) defer C.free(unsafe.Pointer(methodName))
methodSig := cString("(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V")
sharerClass, err := androidLoadClass(env, ctx, "org.julianfamily.keepassgo.AndroidShare") defer C.free(unsafe.Pointer(methodSig))
if err != nil { method := C.jni_GetStaticMethodID(env, sharerClass, methodName, methodSig)
return err if method == nil {
} callErr = androidJNIError(env, "resolve shareVault method")
methodName := cString("shareVault") if callErr == nil {
defer C.free(unsafe.Pointer(methodName)) callErr = fmt.Errorf("resolve shareVault method")
methodSig := cString("(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V") }
defer C.free(unsafe.Pointer(methodSig)) return
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) jPath := androidJavaString(env, path)
jTitle := androidJavaString(env, title) jTitle := androidJavaString(env, title)
args := [3]C.jvalue{} args := [3]C.jvalue{}
args[0] = C.jni_ValueObject(ctx) args[0] = C.jni_ValueObject(ctx)
args[1] = C.jni_ValueObject(C.jobject(jPath)) args[1] = C.jni_ValueObject(C.jobject(jPath))
args[2] = C.jni_ValueObject(C.jobject(jTitle)) args[2] = C.jni_ValueObject(C.jobject(jTitle))
C.jni_CallStaticVoidMethodA(env, sharerClass, method, &args[0]) C.jni_CallStaticVoidMethodA(env, sharerClass, method, &args[0])
if err := androidJNIError(env, "share vault"); err != nil { callErr = androidJNIError(env, "share vault")
return err })
} return callErr
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) { func androidLoadClass(env *C.JNIEnv, ctx C.jobject, name string) (C.jclass, error) {
+28 -4
View File
@@ -444,7 +444,12 @@ func TestUIAPITokenLifecycleManagement(t *testing.T) {
func TestUIAPITokenPolicyRulesCanBeAddedAndRemoved(t *testing.T) { func TestUIAPITokenPolicyRulesCanBeAddedAndRemoved(t *testing.T) {
t.Parallel() 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") u.masterPassword.SetText("correct horse battery staple")
if err := u.createVaultAction(); err != nil { if err := u.createVaultAction(); err != nil {
t.Fatalf("createVaultAction() error = %v", err) t.Fatalf("createVaultAction() error = %v", err)
@@ -582,7 +587,12 @@ func TestPolicyRulePartsFormatsGroupAndEntryResources(t *testing.T) {
func TestUIAPITokenDetailPanelHandlesMissingRemoveClickables(t *testing.T) { func TestUIAPITokenDetailPanelHandlesMissingRemoveClickables(t *testing.T) {
t.Parallel() 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") u.masterPassword.SetText("correct horse battery staple")
if err := u.createVaultAction(); err != nil { if err := u.createVaultAction(); err != nil {
t.Fatalf("createVaultAction() error = %v", err) t.Fatalf("createVaultAction() error = %v", err)
@@ -1615,10 +1625,17 @@ func TestUIOpenRemoteAndSaveThroughConfiguredWebDAVTarget(t *testing.T) {
})) }))
defer server.Close() 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.masterPassword.SetText("correct horse battery staple")
u.remoteBaseURL.SetText(server.URL) u.remoteBaseURL.SetText(server.URL)
u.remotePath.SetText("vaults/main.kdbx") u.remotePath.SetText("vaults/main.kdbx")
u.selectedVaultRemoteProfileID = ""
u.selectedVaultRemoteCredentialEntryID = ""
if err := u.openRemoteAction(); err != nil { if err := u.openRemoteAction(); err != nil {
t.Fatalf("openRemoteAction() error = %v", err) t.Fatalf("openRemoteAction() error = %v", err)
@@ -2059,10 +2076,17 @@ func TestUIRemoteSaveConflictShowsVisibleErrorAndKeepsDirtyState(t *testing.T) {
})) }))
defer server.Close() 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.masterPassword.SetText("correct horse battery staple")
u.remoteBaseURL.SetText(server.URL) u.remoteBaseURL.SetText(server.URL)
u.remotePath.SetText("vaults/main.kdbx") u.remotePath.SetText("vaults/main.kdbx")
u.selectedVaultRemoteProfileID = ""
u.selectedVaultRemoteCredentialEntryID = ""
if err := u.openRemoteAction(); err != nil { if err := u.openRemoteAction(); err != nil {
t.Fatalf("openRemoteAction() error = %v", err) t.Fatalf("openRemoteAction() error = %v", err)