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.
This commit is contained in:
parent
4b74367a55
commit
dc7661e226
BIN
Permissions.jar
Normal file
BIN
Permissions.jar
Normal file
Binary file not shown.
91
Permissions.java
Normal file
91
Permissions.java
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ func handleEvent(e event.Event) {
|
||||||
// view.
|
// view.
|
||||||
if e.View != 0 {
|
if e.View != 0 {
|
||||||
initPgp(e.View)
|
initPgp(e.View)
|
||||||
|
initPermissions(e.View)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +38,10 @@ func initPgp(view uintptr) {
|
||||||
passgo.InitPgp(view)
|
passgo.InitPgp(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initPermissions(view uintptr) {
|
||||||
|
passgo.InitPermissions(view)
|
||||||
|
}
|
||||||
|
|
||||||
func getConfDir() (string, error) {
|
func getConfDir() (string, error) {
|
||||||
ret, err := app.DataDir()
|
ret, err := app.DataDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// +build android
|
// +build android
|
||||||
|
|
||||||
//go:generate mkdir -p classes
|
//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 jar cf PgpConnect.jar -C classes .
|
||||||
//go:generate rm -rf 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() {
|
func stopPgp() {
|
||||||
waitch = make(chan struct{})
|
waitch = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,22 @@ registerFragment(JNIEnv *env, jobject view) {
|
||||||
jobject inst = (*env)->NewObject(env, cls, mid, 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, "<init>", "(Landroid/view/View;)V");
|
||||||
|
jobject inst = (*env)->NewObject(env, cls, mid, view);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GetId(JNIEnv* env, jobject p, int chint) {
|
GetId(JNIEnv* env, jobject p, int chint) {
|
||||||
jclass cls = (*env)->GetObjectClass(env, p);
|
jclass cls = (*env)->GetObjectClass(env, p);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
void registerFragment(JNIEnv *env, jobject view);
|
void registerFragment(JNIEnv *env, jobject view);
|
||||||
|
void registerPermissionsFragment(JNIEnv *env, jobject view);
|
||||||
void GetId(JNIEnv* env, jobject p, int chint);
|
void GetId(JNIEnv* env, jobject p, int chint);
|
||||||
void Decrypt(JNIEnv* env, jobject p, char* cdata, int datalen, 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);
|
void Encrypt(JNIEnv* env, jobject p, char* cid, int idlen, char* cdata, int datalen, int chint);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user