diff --git a/android_share_android.go b/android_share_android.go index 4c7a7ce..865520e 100644 --- a/android_share_android.go +++ b/android_share_android.go @@ -9,18 +9,6 @@ package main #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); } @@ -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) { diff --git a/main_test.go b/main_test.go index 089e1db..8192033 100644 --- a/main_test.go +++ b/main_test.go @@ -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)