passgo/impl_android.go
Greg Pomerantz dc7661e226 Android: launch the 'All files access' settings screen at startup.
MANAGE_EXTERNAL_STORAGE (needed to read the store at
/storage/emulated/0/...) cannot be requested with a runtime
permission dialog; the user must toggle it in system settings.
Port the mechanism from the pad app:

- Permissions.java: a Fragment registered at window attach that
  launches Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
  (per-app settings screen) when Environment.isExternalStorageManager()
  is false on API 30+ (runtime READ_EXTERNAL_STORAGE request on
  older APIs). The guard means the screen appears only until the
  user grants it.
- The compiled classes ship in PgpConnect.jar's directory jar
  (Permissions.jar); gogio picks *.jar up from the package dir and
  dexes them. go:generate now rebuilds it.
- jni_android.c/h: registerPermissionsFragment(), mirroring
  registerFragment() for PgpConnect.
- Impl wiring: call it from the AndroidViewEvent handler right
  after InitPgp.

Verified on the x86_64 emulator: with the appop denied the system
settings screen opens automatically on launch; after granting it,
launch is clean and list + OpenKeychain decryption from
/sdcard/Pass work end to end.
2026-08-21 14:44:38 -04:00

110 lines
2.4 KiB
Go

//go:build android
// +build android
//go:generate mkdir -p classes
//go:generate javac -nowarn -classpath $ANDROID_HOME/platforms/android-35/android.jar:openpgp-api.jar -d classes PgpConnect.java Permissions.java
//go:generate jar cf PgpConnect.jar -C classes .
//go:generate rm -rf classes
package passgo
/*
#import <jni.h>
#import "jni_android.h"
*/
import "C"
import (
"fmt"
"io/ioutil"
"log"
"path"
"strings"
"sync"
"unsafe"
"gioui.org/app"
_ "gioui.org/app/permission/storage"
//"git.wow.st/gmp/jni"
)
var (
jvm uintptr
waitch chan struct{}
//w *app.Window
pgp PGP
installCompleteOnce sync.Once
)
func init() {
waitch = make(chan struct{})
}
//export Java_st_wow_git_passgo_PgpConnect_installComplete
func Java_st_wow_git_passgo_PgpConnect_installComplete(env *C.JNIEnv, class C.jclass, p C.jobject) {
log.Printf("InstallComplete()")
pgp = PGP(C.NewGlobalRef(env, p))
installCompleteOnce.Do(func() {
close(waitch)
})
}
func InitPgp(view uintptr) {
log.Printf("InitPgp()")
jvm = app.JavaVM()
SetJVM(jvm) // why?
RunInJVM(func(env *JNIEnv) {
C.registerFragment(env, (C.jobject)(unsafe.Pointer(view)))
})
}
// InitPermissions registers the Permissions fragment, which requests the
// storage permissions (launching the system "All files access" settings
// screen on Android 11+) so the app can read the store in shared storage.
func InitPermissions(view uintptr) {
log.Printf("InitPermissions()")
RunInJVM(func(env *JNIEnv) {
C.registerPermissionsFragment(env, (C.jobject)(unsafe.Pointer(view)))
})
}
func stopPgp() {
waitch = make(chan struct{})
}
func connect() {
<-waitch
}
// Clip copies a string to the clipboard
func Clip(x string) {
pgp.Clip(x)
}
func (s *Store) nativeDecrypt(name string) (string, error) {
connect()
data, err := ioutil.ReadFile(path.Join(s.Dir, strings.Join([]string{name, ".gpg"}, "")))
if err != nil {
return "", fmt.Errorf("Error reading file: %s", err)
}
ret, err := pgp.Decrypt(string(data))
return ret, err
}
func (s *Store) nativeEncrypt(pw string) ([]byte, error) {
connect()
ret, err := pgp.Encrypt(s.Id, pw)
return []byte(ret), err
}
func nativeIdentities() ([]string, error) {
//NOT IMPLEMENTED ON ANDROID
return []string{}, nil
log.Printf("nativeIdentities()")
connect()
log.Printf("calling pgp.GetId()")
id, err := pgp.GetId()
log.Printf("pgp.GetId() returned")
return []string{id}, err
}