SDL: android: Remove blocking permission request code. Async only in SDL3!

From bc984f78bf420c15c7935359320bb21f26f0ed30 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 12 Feb 2024 19:46:03 -0500
Subject: [PATCH] android: Remove blocking permission request code. Async only
 in SDL3!

(this actually still blocks at our internal points of usage, though, for
replacement at a later time.)
---
 docs/README-migration.md                      |  2 ++
 include/SDL3/SDL_system.h                     | 25 +---------------
 src/audio/aaudio/SDL_aaudio.c                 | 19 +++++++++++-
 src/audio/openslES/SDL_openslES.c             | 25 ++++++++++++++--
 src/core/SDL_core_unsupported.c               | 12 ++------
 src/core/android/SDL_android.c                | 30 +------------------
 src/core/android/SDL_android.h                |  3 --
 src/dynapi/SDL_dynapi.sym                     |  1 -
 src/dynapi/SDL_dynapi_overrides.h             |  1 -
 src/dynapi/SDL_dynapi_procs.h                 |  3 +-
 src/hidapi/android/hid.cpp                    | 25 +++++++++++++++-
 src/video/android/SDL_android_video_capture.c |  4 +--
 12 files changed, 73 insertions(+), 77 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index dfd68bf65765..5f8555a31bd4 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1424,6 +1424,8 @@ SDL_WindowsMessageHook has changed signatures so the message may be modified and
 
 SDL_AndroidGetExternalStorageState() takes the state as an output parameter and returns 0 if the function succeeds or a negative error code if there was an error.
 
+SDL_AndroidRequestPermission is no longer a blocking call; the caller now provides a callback function that fires when a response is available.
+
 The following functions have been removed:
 * SDL_RenderGetD3D11Device() - replaced with the "SDL.renderer.d3d11.device" property
 * SDL_RenderGetD3D12Device() - replaced with the "SDL.renderer.d3d12.device" property
diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h
index 277f512a5b72..40483fc646a7 100644
--- a/include/SDL3/SDL_system.h
+++ b/include/SDL3/SDL_system.h
@@ -399,29 +399,6 @@ extern DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(Uint32 *state);
  */
 extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(void);
 
-/**
- * Request permissions at runtime.
- *
- * You do not need to call this for built-in functionality of SDL; recording
- * from a microphone or reading images from a camera, using standard SDL
- * APIs, will manage permission requests for you.
- *
- * This blocks the calling thread until the permission is granted or denied.
- * if the app already has the requested permission, this returns immediately,
- * but may block indefinitely until the user responds to the system's
- * permission request dialog.
- *
- * If possible, you should _not_ use this function. You should use
- * SDL_AndroidRequestPermissionAsync and deal with the response in a callback
- * at a later time, and possibly in a different thread.
- *
- * \param permission The permission to request.
- * \returns SDL_TRUE if the permission was granted, SDL_FALSE otherwise.
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission);
-
 
 typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, const char *permission, SDL_bool granted);
 
@@ -453,7 +430,7 @@ typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, con
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC int SDLCALL SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
+extern DECLSPEC int SDLCALL SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
 
 /**
  * Shows an Android toast notification.
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index 4445ba8c85ed..265eaacaf63b 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -381,6 +381,12 @@ static int BuildAAudioStream(SDL_AudioDevice *device)
     return 0;
 }
 
+// !!! FIXME: make this non-blocking!
+static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
+{
+    SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
+}
+
 static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
 {
 #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
@@ -390,7 +396,18 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
     LOGI(__func__);
 
     if (device->iscapture) {
-        if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) {
+        // !!! FIXME: make this non-blocking!
+        SDL_AtomicInt permission_response;
+        SDL_AtomicSet(&permission_response, 0);
+        if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
+            return -1;
+        }
+
+        while (SDL_AtomicGet(&permission_response) == 0) {
+            SDL_Delay(10);
+        }
+
+        if (SDL_AtomicGet(&permission_response) < 0) {
             LOGI("This app doesn't have RECORD_AUDIO permission");
             return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
         }
diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c
index 84f92a41b8df..bda1b91c9716 100644
--- a/src/audio/openslES/SDL_openslES.c
+++ b/src/audio/openslES/SDL_openslES.c
@@ -228,6 +228,12 @@ static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
     }
 }
 
+// !!! FIXME: make this non-blocking!
+static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
+{
+    SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
+}
+
 static int OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
 {
     struct SDL_PrivateAudioData *audiodata = device->hidden;
@@ -241,9 +247,22 @@ static int OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
     SLresult result;
     int i;
 
-    if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) {
-        LOGE("This app doesn't have RECORD_AUDIO permission");
-        return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
+    // !!! FIXME: make this non-blocking!
+    {
+        SDL_AtomicInt permission_response;
+        SDL_AtomicSet(&permission_response, 0);
+        if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
+            return -1;
+        }
+
+        while (SDL_AtomicGet(&permission_response) == 0) {
+            SDL_Delay(10);
+        }
+
+        if (SDL_AtomicGet(&permission_response) < 0) {
+            LOGE("This app doesn't have RECORD_AUDIO permission");
+            return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
+        }
     }
 
     // Just go with signed 16-bit audio as it's the most compatible
diff --git a/src/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c
index 6ab9ff54e02f..2196aa40447f 100644
--- a/src/core/SDL_core_unsupported.c
+++ b/src/core/SDL_core_unsupported.c
@@ -161,17 +161,9 @@ void *SDL_AndroidGetJNIEnv()
     return NULL;
 }
 
-DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission);
-SDL_bool SDL_AndroidRequestPermission(const char *permission)
-{
-    (void)permission;
-    SDL_Unsupported();
-    return SDL_FALSE;
-}
-
 typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, const char *permission, SDL_bool granted);
-DECLSPEC int SDLCALL SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
-int SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
+DECLSPEC int SDLCALL SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
+int SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
 {
     (void)permission;
     (void)cb;
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 9c94e2549984..a8a9cddb731f 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -2557,11 +2557,6 @@ const char *SDL_AndroidGetExternalStoragePath(void)
     return s_AndroidExternalFilesPath;
 }
 
-SDL_bool SDL_AndroidRequestPermission(const char *permission)
-{
-    return Android_JNI_RequestPermission(permission);
-}
-
 int SDL_AndroidShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset)
 {
     return Android_JNI_ShowToast(message, duration, gravity, xOffset, yOffset);
@@ -2662,7 +2657,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
     SDL_assert(!"Shouldn't have hit this code");  // we had a permission response for a request we never made...?
 }
 
-int SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
+int SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
 {
     if (!permission) {
         return SDL_InvalidParamError("permission");
@@ -2700,29 +2695,6 @@ int SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequest
     return 0;
 }
 
-static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
-{
-    SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
-}
-
-SDL_bool Android_JNI_RequestPermission(const char *permission)
-{
-    SDL_AtomicInt response;
-    SDL_AtomicSet(&response, 0);
-
-    if (SDL_AndroidRequestPermissionAsync(permission, AndroidRequestPermissionBlockingCallback, &response) == -1) {
-        return SDL_FALSE;
-    }
-
-    /* Wait for the request to complete */
-    while (SDL_AtomicGet(&response) == 0) {
-        SDL_Delay(10);
-    }
-
-    return (SDL_AtomicGet(&response) < 0) ? SDL_FALSE : SDL_TRUE;
-}
-
-
 /* Show toast notification */
 int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset)
 {
diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h
index b611bc52db7b..c51fb4db9aca 100644
--- a/src/core/android/SDL_android.h
+++ b/src/core/android/SDL_android.h
@@ -125,9 +125,6 @@ SDL_bool Android_JNI_SetSystemCursor(int cursorID);
 SDL_bool Android_JNI_SupportsRelativeMouse(void);
 SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled);
 
-/* Request permission */
-SDL_bool Android_JNI_RequestPermission(const char *permission);
-
 /* Show toast notification */
 int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset);
 
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 821ad6f2294b..2ba1f7be6a87 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -972,7 +972,6 @@ SDL3_0.0.0 {
     SDL_RenderGeometryRawFloat;
     SDL_SetWindowShape;
     SDL_RenderViewportSet;
-    SDL_AndroidRequestPermissionAsync;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 8168d51ff837..b297a3026ecf 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -997,4 +997,3 @@
 #define SDL_RenderGeometryRawFloat SDL_RenderGeometryRawFloat_REAL
 #define SDL_SetWindowShape SDL_SetWindowShape_REAL
 #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL
-#define SDL_AndroidRequestPermissionAsync SDL_AndroidRequestPermissionAsync_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 15bd3cf28d9d..3969fda9c95f 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -93,7 +93,7 @@ SDL_DYNAPI_PROC(const char*,SDL_AndroidGetExternalStoragePath,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_AndroidGetExternalStorageState,(Uint32 *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_AndroidGetInternalStoragePath,(void),(),return)
 SDL_DYNAPI_PROC(void*,SDL_AndroidGetJNIEnv,(void),(),return)
-SDL_DYNAPI_PROC(SDL_bool,SDL_AndroidRequestPermission,(const char *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_AndroidRequestPermission,(const char *a, SDL_AndroidRequestPermissionCallback b, void *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_AndroidSendMessage,(Uint32 a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_AndroidShowToast,(const char *a, int b, int c, int d, int e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return)
@@ -1022,4 +1022,3 @@ SDL_DYNAPI_PROC(int,SDL_GetRenderColorScale,(SDL_Renderer *a, float *b),(a,b),re
 SDL_DYNAPI_PROC(int,SDL_RenderGeometryRawFloat,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const SDL_FColor *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowShape,(SDL_Window *a, SDL_Surface *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_RenderViewportSet,(SDL_Renderer *a),(a),return)
-SDL_DYNAPI_PROC(int,SDL_AndroidRequestPermissionAsync,(const char *a, SDL_AndroidRequestPermissionCallback b, void *c),(a,b,c),return)
diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp
index 098feb85d868..ea8d2797280d 100644
--- a/src/hidapi/android/hid.cpp
+++ b/src/hidapi/android/hid.cpp
@@ -1029,6 +1029,29 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse
 extern "C"
 {
 
+// !!! FIXME: make this non-blocking!
+static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
+{
+    SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
+}
+
+static SDL_bool RequestBluetoothPermissions(const char *permission)
+{
+    // !!! FIXME: make this non-blocking!
+    SDL_AtomicInt permission_response;
+    SDL_AtomicSet(&permission_response, 0);
+    if (SDL_AndroidRequestPermission(permission, AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
+        return SDL_FALSE;
+    }
+
+    while (SDL_AtomicGet(&permission_response) == 0) {
+        SDL_Delay(10);
+    }
+
+    return SDL_AtomicGet(&permission_response) > 0;
+}
+
+
 int hid_init(void)
 {
 	if ( !g_initialized && g_HIDDeviceManagerCallbackHandler )
@@ -1046,7 +1069,7 @@ int hid_init(void)
 			bool init_bluetooth = false;
 			if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE)) {
 				if (SDL_GetAndroidSDKVersion() < 31 ||
-					Android_JNI_RequestPermission("android.permission.BLUETOOTH_CONNECT")) {
+					RequestBluetoothPermissions("android.permission.BLUETOOTH_CONNECT")) {
 					init_bluetooth = true;
 				}
 			}
diff --git a/src/video/android/SDL_android_video_capture.c b/src/video/android/SDL_android_video_capture.c
index 3ec5cd1bafe5..ff157cbd41c9 100644
--- a/src/video/android/SDL_android_video_capture.c
+++ b/src/video/android/SDL_android_video_capture.c
@@ -68,12 +68,12 @@ static void
 create_cameraMgr(void)
 {
     if (cameraMgr == NULL) {
-
+        #if 0  // !!! FIXME: this is getting replaced in a different branch.
         if (!Android_JNI_RequestPermission("android.permission.CAMERA")) {
             SDL_SetError("This app doesn't have CAMERA permission");
             return;
         }
-
+        #endif
         cameraMgr = ACameraManager_create();
         if (cameraMgr == NULL) {
             SDL_Log("Error creating ACameraManager");