diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index fcc288789..5bf310cf6 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -284,7 +284,8 @@ Error AudioDriverOpenSL::capture_start() { return capture_init_device(); } - return OK; + WARN_PRINT("Unable to start audio capture - No RECORD_AUDIO permission"); + return ERR_UNAUTHORIZED; } Error AudioDriverOpenSL::capture_stop() { diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index 37c3adbd0..84dbf6419 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -21,6 +21,8 @@ + + () override fun onCreate(savedInstanceState : Bundle?) { - PermissionsUtil.requestManifestPermissions(this); + // We exclude certain permissions from the set we request at startup, as they'll be + // requested on demand based on use-cases. + PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) val params : Array? = getIntent().getStringArrayExtra(COMMAND_LINE_PARAMS); @@ -110,6 +112,7 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { val longPressEnabled = enableLongPressGestures() val panScaleEnabled = enablePanAndScaleGestures() + checkForProjectPermissionsToEnable() runOnUiThread { // Enable long press, panning and scaling gestures @@ -120,6 +123,17 @@ open class PandemoniumEditor : FullScreenPandemoniumApp() { } } + /** + * Check for project permissions to enable + */ + protected open fun checkForProjectPermissionsToEnable() { + // Check for RECORD_AUDIO permission + val audioInputEnabled = java.lang.Boolean.parseBoolean(pandemoniumLib.getGlobal("audio/enable_audio_input")); + if (audioInputEnabled) { + PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this) + } + } + private fun updateCommandLineParams(args: Array?) { // Update the list of command line params with the new args commandLineParams.clear() diff --git a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumGame.kt b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumGame.kt index 82d5cccca..35b1baee1 100644 --- a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumGame.kt +++ b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumGame.kt @@ -39,4 +39,9 @@ class PandemoniumGame : PandemoniumEditor() { override fun enableLongPressGestures() = false override fun enablePanAndScaleGestures() = false + + override fun checkForProjectPermissionsToEnable() { + // Nothing to do.. by the time we get here, the project permissions will have already + // been requested by the Editor window. + } } diff --git a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumProjectManager.kt b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumProjectManager.kt index 3cd44d770..85baf22d8 100644 --- a/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumProjectManager.kt +++ b/platform/android/java/editor/src/main/java/net/relintai/pandemonium/editor/PandemoniumProjectManager.kt @@ -38,5 +38,9 @@ package net.relintai.pandemonium.editor * [PandemoniumEditor] activity. */ -class PandemoniumProjectManager : PandemoniumEditor() +class PandemoniumProjectManager : PandemoniumEditor() { + override fun checkForProjectPermissionsToEnable() { + // Nothing to do here.. we have yet to select a project to load. + } +} diff --git a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/utils/PermissionsUtil.java b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/utils/PermissionsUtil.java index 520abae28..c4be96a9b 100644 --- a/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/net/relintai/pandemonium/pandemonium/utils/PermissionsUtil.java @@ -42,10 +42,12 @@ import android.os.Environment; import android.provider.Settings; import android.util.Log; +import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * This class includes utility functions for Android permissions related operations. @@ -57,7 +59,8 @@ public final class PermissionsUtil { static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; static final int REQUEST_CAMERA_PERMISSION = 2; static final int REQUEST_VIBRATE_PERMISSION = 3; - public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001; + public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001; + public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002; public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002; private PermissionsUtil() { @@ -65,31 +68,55 @@ public final class PermissionsUtil { /** * Request a dangerous permission. name must be specified in this - * @param name the name of the requested permission. + * @param permissionName the name of the requested permission. * @param activity the caller activity for this method. * @return true/false. "true" if permission was granted otherwise returns "false". */ - public static boolean requestPermission(String name, Activity activity) { + public static boolean requestPermission(String permissionName, Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Not necessary, asked on install already return true; } - if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); - return false; - } + switch (permissionName) { + case "RECORD_AUDIO": + case Manifest.permission.RECORD_AUDIO: + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); + return false; + } + return true; + case "CAMERA": + case Manifest.permission.CAMERA: + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); + return false; + } + return true; + case "VIBRATE": + case Manifest.permission.VIBRATE: + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); + return false; + } + return true; - if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); - return false; + default: + // Check if the given permission is a dangerous permission + try { + PermissionInfo permissionInfo = getPermissionInfo(activity, permissionName); + int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { permissionName }, REQUEST_SINGLE_PERMISSION_REQ_CODE); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + // Unknown permission - return false as it can't be granted. + Log.w(TAG, "Unable to identify permission " + permissionName, e); + return false; + } + return true; } - - if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { - activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); - return false; - } - return true; } /** @@ -98,6 +125,16 @@ public final class PermissionsUtil { * @return true/false. "true" if all permissions were granted otherwise returns "false". */ public static boolean requestManifestPermissions(Activity activity) { + return requestManifestPermissions(activity, null); + } + + /** + * Request dangerous permissions which are defined in the Android manifest file from the user. + * @param activity the caller activity for this method. + * @param excludes Set of permissions to exclude from the request + * @return true/false. "true" if all permissions were granted otherwise returns "false". + */ + public static boolean requestManifestPermissions(Activity activity, @Nullable Set excludes) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } @@ -115,8 +152,12 @@ public final class PermissionsUtil { List requestedPermissions = new ArrayList<>(); for (String manifestPermission : manifestPermissions) { + if (excludes != null && excludes.contains(manifestPermission)) { + continue; + } + try { - if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) { + if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { try { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);