diff --git a/Permissions.jar b/Permissions.jar new file mode 100644 index 0000000..b04befc Binary files /dev/null and b/Permissions.jar differ diff --git a/Permissions.java b/Permissions.java new file mode 100644 index 0000000..68472d6 --- /dev/null +++ b/Permissions.java @@ -0,0 +1,91 @@ +package st.wow.git.passgo; + +import java.lang.Runnable; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.content.Context; +import android.util.Log; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.Manifest; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.provider.Settings; +import android.view.View; + +// Requests the storage permissions the app needs to read the password +// store in shared storage. On Android 11+ (API 30+) the +// MANAGE_EXTERNAL_STORAGE ("All files access") permission cannot be +// requested with a dialog; the user must toggle it in the system +// settings, so we launch that settings screen directly for this app. +public class Permissions extends Fragment { + Context ctx; + Handler handler; + final int PERMISSIONS_REQUEST = 1; + + public Permissions(View view) { + Log.d("passgo", "Permissions()"); + this.ctx = view.getContext(); + this.handler = new Handler(this.ctx.getMainLooper()); + Permissions inst = this; + handler.post(new Runnable() { + public void run() { + Activity act = (Activity)ctx; + FragmentTransaction ft = act.getFragmentManager().beginTransaction(); + ft.add(inst, "Permissions"); + ft.commitNow(); + } + }); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + Log.d("passgo", "Permissions onAttach()"); + + if (!(context instanceof Activity)) { + Log.w("passgo", "Context is not an Activity"); + return; + } + + Activity activity = (Activity) context; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + if (context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions( + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + PERMISSIONS_REQUEST + ); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + && !Environment.isExternalStorageManager()) { + Log.d("passgo", "Requesting all files access"); + Intent intent = new Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + Uri.parse("package:" + activity.getPackageName()) + ); + if (intent.resolveActivity(activity.getPackageManager()) != null) { + startActivity(intent); + } else { + Intent fallback = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + if (fallback.resolveActivity(activity.getPackageManager()) != null) { + startActivity(fallback); + } else { + Log.e("passgo", "No activity found for all files access settings"); + } + } + } + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d("passgo", "Permissions onActivityResult(" + requestCode + "): " + resultCode); + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/cmd/passgo-gui/impl_android.go b/cmd/passgo-gui/impl_android.go index e1b296a..6bd83d6 100644 --- a/cmd/passgo-gui/impl_android.go +++ b/cmd/passgo-gui/impl_android.go @@ -29,6 +29,7 @@ func handleEvent(e event.Event) { // view. if e.View != 0 { initPgp(e.View) + initPermissions(e.View) } } } @@ -37,6 +38,10 @@ func initPgp(view uintptr) { passgo.InitPgp(view) } +func initPermissions(view uintptr) { + passgo.InitPermissions(view) +} + func getConfDir() (string, error) { ret, err := app.DataDir() if err != nil { diff --git a/impl_android.go b/impl_android.go index aac5da8..dc5b806 100644 --- a/impl_android.go +++ b/impl_android.go @@ -2,7 +2,7 @@ // +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 +//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 @@ -58,6 +58,16 @@ func InitPgp(view uintptr) { }) } +// 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{}) } diff --git a/jni_android.c b/jni_android.c index c1c63d3..3a37a05 100644 --- a/jni_android.c +++ b/jni_android.c @@ -21,6 +21,22 @@ registerFragment(JNIEnv *env, jobject view) { jobject inst = (*env)->NewObject(env, cls, mid, view); } +void +registerPermissionsFragment(JNIEnv *env, jobject view) { + jclass cls = (*env)->GetObjectClass(env, view); + jmethodID mid = (*env)->GetMethodID(env, cls, "getContext", "()Landroid/content/Context;"); + jobject ctx = (*env)->CallObjectMethod(env, view, mid); + cls = (*env)->GetObjectClass(env, ctx); + mid = (*env)->GetMethodID(env, cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject loader = (*env)->CallObjectMethod(env, ctx, mid); + cls = (*env)->GetObjectClass(env, loader); + mid = (*env)->GetMethodID(env, cls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + jstring str = (*env)->NewStringUTF(env, "st/wow/git/passgo/Permissions"); + cls = (*env)->CallObjectMethod(env, loader, mid, str); + mid = (*env)->GetMethodID(env, cls, "", "(Landroid/view/View;)V"); + jobject inst = (*env)->NewObject(env, cls, mid, view); +} + void GetId(JNIEnv* env, jobject p, int chint) { jclass cls = (*env)->GetObjectClass(env, p); diff --git a/jni_android.h b/jni_android.h index ee5eb43..861bca4 100644 --- a/jni_android.h +++ b/jni_android.h @@ -1,6 +1,7 @@ #include void registerFragment(JNIEnv *env, jobject view); +void registerPermissionsFragment(JNIEnv *env, jobject view); void GetId(JNIEnv* env, jobject p, int chint); void Decrypt(JNIEnv* env, jobject p, char* cdata, int datalen, int chint); void Encrypt(JNIEnv* env, jobject p, char* cid, int idlen, char* cdata, int datalen, int chint);