Browse Source

android: Added SDL_AndroidRequestPermissionAsync.

Ryan C. Gordon 1 year ago
parent
commit
af61cfd5e0

+ 44 - 0
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.
  *

+ 10 - 0
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)
 {

+ 83 - 23
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)
 {

+ 1 - 0
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: *;
 };

+ 1 - 0
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

+ 1 - 0
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)

+ 4 - 0
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