diff --git a/include/SDL3/SDL_system.h b/include/SDL3/SDL_system.h index 11deb0d8ca..277f512a5b 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 2bef98abce..6ab9ff54e0 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 852e11e5a4..9c94e25499 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_AtomicSet(&bPermissionRequestPending, SDL_TRUE); - jpermission = (*env)->NewStringUTF(env, permission); - (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, requestCode); + 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"); + } + + 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 2ba1f7be6a..821ad6f229 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 b297a3026e..8168d51ff8 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 d841325c31..15bd3cf28d 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 805e77fa29..af6cd5f3ce 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