From af61cfd5e04a395e11921faa755e58ef196f2847 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 12 Feb 2024 15:26:09 -0500
Subject: [PATCH] android: Added SDL_AndroidRequestPermissionAsync.
---
include/SDL3/SDL_system.h | 44 ++++++++++++
src/core/SDL_core_unsupported.c | 10 +++
src/core/android/SDL_android.c | 106 ++++++++++++++++++++++------
src/dynapi/SDL_dynapi.sym | 1 +
src/dynapi/SDL_dynapi_overrides.h | 1 +
src/dynapi/SDL_dynapi_procs.h | 1 +
src/dynapi/SDL_dynapi_unsupported.h | 4 ++
7 files changed, 144 insertions(+), 23 deletions(-)
diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h
index 11deb0d8cab0..277f512a5b72 100644
--- a/include/SDL3/SDL_system.h
+++ b/include/SDL3/SDL_system.h
@@ -402,7 +402,18 @@ 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.
@@ -411,6 +422,39 @@ extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(void);
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission);
+
+typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, const char *permission, SDL_bool granted);
+
+/**
+ * Request permissions at runtime, asynchronously.
+ *
+ * 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 function never blocks. Instead, the app-supplied callback will be
+ * called when a decision has been made. This callback may happen on a
+ * different thread, and possibly much later, as it might wait on a user to
+ * respond to a system dialog. If permission has already been granted for
+ * a specific entitlement, the callback will still fire, probably on the
+ * current thread and before this function returns.
+ *
+ * If the request submission fails, this function returns -1 and the
+ * callback will NOT be called, but this should only happen in
+ * catastrophic conditions, like memory running out. Normally there will
+ * be a yes or no to the request through the callback.
+ *
+ * \param permission The permission to request.
+ * \param cb The callback to trigger when the request has a response.
+ * \param userdata An app-controlled pointer that is passed to the callback.
+ * \returns zero if the request was submitted, -1 if there was an error
+ * submitting. The result of the request is only ever reported
+ * through the callback, not this return value.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_AndroidRequestPermissionAsync(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
+
/**
* Shows an Android toast notification.
*
diff --git a/src/core/SDL_core_unsupported.c b/src/core/SDL_core_unsupported.c
index 2bef98abce2e..6ab9ff54e02f 100644
--- a/src/core/SDL_core_unsupported.c
+++ b/src/core/SDL_core_unsupported.c
@@ -169,6 +169,16 @@ SDL_bool SDL_AndroidRequestPermission(const char *permission)
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)
+{
+ (void)permission;
+ (void)cb;
+ (void)userdata;
+ return SDL_Unsupported();
+}
+
DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param);
int SDL_AndroidSendMessage(Uint32 command, int param)
{
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 852e11e5a481..9c94e2549984 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -381,9 +381,6 @@ static SDL_bool bHasNewData;
static SDL_bool bHasEnvironmentVariables;
-static SDL_AtomicInt bPermissionRequestPending;
-static SDL_bool bPermissionRequestResult;
-
/* Android AssetManager */
static void Internal_Android_Create_AssetManager(void);
static void Internal_Android_Destroy_AssetManager(void);
@@ -997,14 +994,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
(*env)->ReleaseStringUTFChars(env, name, utfname);
}
-JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
- JNIEnv *env, jclass cls,
- jint requestCode, jboolean result)
-{
- bPermissionRequestResult = result;
- SDL_AtomicSet(&bPermissionRequestPending, SDL_FALSE);
-}
-
JNIEXPORT void JNICALL
SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
jstring name, jint device_id)
@@ -2640,29 +2629,100 @@ SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled)
return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
}
-SDL_bool Android_JNI_RequestPermission(const char *permission)
+typedef struct NativePermissionRequestInfo
{
- JNIEnv *env = Android_JNI_GetEnv();
- jstring jpermission;
- const int requestCode = 1;
+ int request_code;
+ char *permission;
+ SDL_AndroidRequestPermissionCallback callback;
+ void *userdata;
+ struct NativePermissionRequestInfo *next;
+} NativePermissionRequestInfo;
- /* Wait for any pending request on another thread */
- while (SDL_AtomicGet(&bPermissionRequestPending) == SDL_TRUE) {
- SDL_Delay(10);
+static NativePermissionRequestInfo pending_permissions;
+
+JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
+ JNIEnv *env, jclass cls,
+ jint requestCode, jboolean result)
+{
+ SDL_LockMutex(Android_ActivityMutex);
+ NativePermissionRequestInfo *prev = &pending_permissions;
+ for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) {
+ if (info->request_code == (int) requestCode) {
+ prev->next = info->next;
+ SDL_UnlockMutex(Android_ActivityMutex);
+ info->callback(info->userdata, info->permission, result ? SDL_TRUE : SDL_FALSE);
+ SDL_free(info->permission);
+ SDL_free(info);
+ return;
+ }
+ prev = info;
+ }
+
+ SDL_UnlockMutex(Android_ActivityMutex);
+ 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)
+{
+ if (!permission) {
+ return SDL_InvalidParamError("permission");
+ } else if (!cb) {
+ return SDL_InvalidParamError("cb");
}
- SDL_AtomicSet(&bPermissionRequestPending, SDL_TRUE);
- jpermission = (*env)->NewStringUTF(env, permission);
- (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, requestCode);
+ NativePermissionRequestInfo *info = (NativePermissionRequestInfo *) SDL_calloc(1, sizeof (NativePermissionRequestInfo));
+ if (!info) {
+ return -1;
+ }
+
+ info->permission = SDL_strdup(permission);
+ if (!info->permission) {
+ SDL_free(info);
+ return -1;
+ }
+
+ static SDL_AtomicInt next_request_code;
+ info->request_code = SDL_AtomicAdd(&next_request_code, 1);
+
+ info->callback = cb;
+ info->userdata = userdata;
+
+ SDL_LockMutex(Android_ActivityMutex);
+ info->next = pending_permissions.next;
+ pending_permissions.next = info;
+ SDL_UnlockMutex(Android_ActivityMutex);
+
+ JNIEnv *env = Android_JNI_GetEnv();
+ jstring jpermission = (*env)->NewStringUTF(env, permission);
+ (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, info->request_code);
(*env)->DeleteLocalRef(env, jpermission);
+ 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(&bPermissionRequestPending) == SDL_TRUE) {
+ while (SDL_AtomicGet(&response) == 0) {
SDL_Delay(10);
}
- return bPermissionRequestResult;
+
+ 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/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 2ba1f7be6a87..821ad6f2294b 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -972,6 +972,7 @@ 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 b297a3026ecf..8168d51ff837 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -997,3 +997,4 @@
#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 d841325c3137..15bd3cf28d9d 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1022,3 +1022,4 @@ 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/dynapi/SDL_dynapi_unsupported.h b/src/dynapi/SDL_dynapi_unsupported.h
index 805e77fa29a6..af6cd5f3ce5a 100644
--- a/src/dynapi/SDL_dynapi_unsupported.h
+++ b/src/dynapi/SDL_dynapi_unsupported.h
@@ -45,4 +45,8 @@ typedef int SDL_WinRT_Path;
typedef struct XUserHandle XUserHandle;
#endif
+#ifndef SDL_PLATFORM_ANDROID
+typedef void *SDL_AndroidRequestPermissionCallback;
+#endif
+
#endif