diff --git a/frontend/drivers/platform_linux.c b/frontend/drivers/platform_linux.c index 0f68a4c0d0e5..d0fc3013d141 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 7fe9dc83f726..762887b24cb5 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 b9de3c3a0087..07e7d9d95637 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 82181fe16aeb..319851bab09c 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 06e1a8c7bab7..48a858976ff1 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 void onRetroArchExit() 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();