From b1e94e8215b24f12b5e69a0053bfbbe0bf733ab5 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Mon, 20 Mar 2017 05:25:05 +0000 Subject: [PATCH 1/2] android: add runtime permission checking for Android 6.0+ --- frontend/drivers/platform_linux.c | 6 ++ frontend/drivers/platform_linux.h | 1 + pkg/android/phoenix/AndroidManifest.xml | 2 +- pkg/android/phoenix/project.properties | 2 +- .../retroactivity/RetroActivityCommon.java | 83 +++++++++++++++++++ 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/frontend/drivers/platform_linux.c b/frontend/drivers/platform_linux.c index 0f68a4c0d0..d0fc3013d1 100644 --- a/frontend/drivers/platform_linux.c +++ b/frontend/drivers/platform_linux.c @@ -1463,6 +1463,10 @@ static void frontend_linux_get_env(int *argc, strlcpy(app_dir, argv, sizeof(app_dir)); (*env)->ReleaseStringUTFChars(env, jstr, argv); + /* Check for runtime permissions on Android 6.0+ */ + if (env && android_app->checkRuntimePermissions) + CALL_VOID_METHOD(env, android_app->activity->clazz, android_app->checkRuntimePermissions); + //set paths depending on the ability to write to internal_storage_path if(!string_is_empty(internal_storage_path)) @@ -1867,6 +1871,8 @@ static void frontend_linux_init(void *data) "onRetroArchExit", "()V"); GET_METHOD_ID(env, android_app->isAndroidTV, class, "isAndroidTV", "()Z"); + GET_METHOD_ID(env, android_app->checkRuntimePermissions, class, + "checkRuntimePermissions", "()V"); CALL_OBJ_METHOD(env, obj, android_app->activity->clazz, android_app->getIntent); diff --git a/frontend/drivers/platform_linux.h b/frontend/drivers/platform_linux.h index 7fe9dc83f7..762887b24c 100644 --- a/frontend/drivers/platform_linux.h +++ b/frontend/drivers/platform_linux.h @@ -161,6 +161,7 @@ struct android_app jmethodID getPendingIntentDownloadsLocation; jmethodID getPendingIntentScreenshotsLocation; jmethodID isAndroidTV; + jmethodID checkRuntimePermissions; }; diff --git a/pkg/android/phoenix/AndroidManifest.xml b/pkg/android/phoenix/AndroidManifest.xml index b9de3c3a00..07e7d9d956 100644 --- a/pkg/android/phoenix/AndroidManifest.xml +++ b/pkg/android/phoenix/AndroidManifest.xml @@ -9,7 +9,7 @@ + android:targetSdkVersion="23" /> diff --git a/pkg/android/phoenix/project.properties b/pkg/android/phoenix/project.properties index 82181fe16a..319851bab0 100644 --- a/pkg/android/phoenix/project.properties +++ b/pkg/android/phoenix/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-22 +target=android-23 android.library.reference.1=libs/googleplay diff --git a/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java b/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java index 06e1a8c7ba..48a858976f 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java @@ -1,15 +1,23 @@ package com.retroarch.browser.retroactivity; +import java.util.List; +import java.util.ArrayList; import com.retroarch.browser.preferences.util.UserPreferences; import android.content.res.Configuration; import android.app.UiModeManager; import android.util.Log; +import android.content.pm.PackageManager; +import android.Manifest; +import android.content.DialogInterface; +import android.app.AlertDialog; /** * Class which provides common methods for RetroActivity related classes. */ public class RetroActivityCommon extends RetroActivityLocation { + final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; + // Exiting cleanly from NDK seems to be nearly impossible. // Have to use exit(0) to avoid weird things happening, even with runOnUiThread() approaches. // Use a separate JNI function to explicitly trigger the readback. @@ -18,6 +26,81 @@ public class RetroActivityCommon extends RetroActivityLocation finish(); } + public void showMessageOKCancel(String message, DialogInterface.OnClickListener onClickListener) + { + new AlertDialog.Builder(this).setMessage(message) + .setPositiveButton("OK", onClickListener).setCancelable(false) + .setNegativeButton("Cancel", null).create().show(); + } + + private boolean addPermission(List permissionsList, String permission) + { + if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + permissionsList.add(permission); + // Check for Rationale Option + if (!shouldShowRequestPermissionRationale(permission)) + return false; + } + return true; + } + + public void checkRuntimePermissions() + { + runOnUiThread(new Runnable() { + public void run() { + checkRuntimePermissionsRunnable(); + } + }); + } + + public void checkRuntimePermissionsRunnable() + { + if (android.os.Build.VERSION.SDK_INT >= 23) + { + // Android 6.0+ needs runtime permission checks + List permissionsNeeded = new ArrayList(); + final List permissionsList = new ArrayList(); + + if (!addPermission(permissionsList, Manifest.permission.READ_EXTERNAL_STORAGE)) + permissionsNeeded.add("Read External Storage"); + if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE)) + permissionsNeeded.add("Write External Storage"); + + if (permissionsList.size() > 0) + { + if (permissionsNeeded.size() > 0) + { + // Need Rationale + Log.i("RetroActivity", "Need to request external storage permissions."); + + String message = "You need to grant access to " + permissionsNeeded.get(0); + + for (int i = 1; i < permissionsNeeded.size(); i++) + message = message + ", " + permissionsNeeded.get(i); + + showMessageOKCancel(message, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), + REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); + + Log.i("RetroActivity", "User accepted request for external storage permissions."); + } + }); + } + else + { + requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), + REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS); + + Log.i("RetroActivity", "Requested external storage permissions."); + } + } + } + } + public boolean isAndroidTV() { Configuration config = getResources().getConfiguration(); From 61cbb25b12e21233ec6fc7691d850e4c7bd434d7 Mon Sep 17 00:00:00 2001 From: Brad Parker Date: Mon, 20 Mar 2017 14:46:43 +0000 Subject: [PATCH 2/2] add callback for permission request result, style nits --- .../retroactivity/RetroActivityCommon.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java b/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java index 48a858976f..df4c1122a2 100644 --- a/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java +++ b/pkg/android/phoenix/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java @@ -35,12 +35,15 @@ public class RetroActivityCommon extends RetroActivityLocation private boolean addPermission(List permissionsList, String permission) { - if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) + { permissionsList.add(permission); + // Check for Rationale Option if (!shouldShowRequestPermissionRationale(permission)) return false; - } + } + return true; } @@ -79,7 +82,8 @@ public class RetroActivityCommon extends RetroActivityLocation message = message + ", " + permissionsNeeded.get(i); showMessageOKCancel(message, - new DialogInterface.OnClickListener() { + new DialogInterface.OnClickListener() + { @Override public void onClick(DialogInterface dialog, int which) { @@ -101,6 +105,24 @@ public class RetroActivityCommon extends RetroActivityLocation } } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + for (int i = 0; i < permissions.length; i++) + { + if(grantResults[i] == PackageManager.PERMISSION_GRANTED) + { + Log.i("RetroActivity", "Permission: " + permissions[i] + " was granted."); + } + else + { + Log.i("RetroActivity", "Permission: " + permissions[i] + " was not granted."); + } + } + } + public boolean isAndroidTV() { Configuration config = getResources().getConfiguration();