Use Gio JVM bridge for Android sharing
This commit is contained in:
+35
-59
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user