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.
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "<init>", "(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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <jni.h>
|
||||
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user