android: Added SDL_AndroidRequestPermissionAsync.

This commit is contained in:
Ryan C. Gordon 2024-02-12 15:26:09 -05:00
parent 310f21bf84
commit af61cfd5e0
7 changed files with 144 additions and 23 deletions

View file

@ -402,7 +402,18 @@ extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(void);
/** /**
* Request permissions at runtime. * 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. * 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. * \param permission The permission to request.
* \returns SDL_TRUE if the permission was granted, SDL_FALSE otherwise. * \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); 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. * Shows an Android toast notification.
* *

View file

@ -169,6 +169,16 @@ SDL_bool SDL_AndroidRequestPermission(const char *permission)
return SDL_FALSE; 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); DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param);
int SDL_AndroidSendMessage(Uint32 command, int param) int SDL_AndroidSendMessage(Uint32 command, int param)
{ {

View file

@ -381,9 +381,6 @@ static SDL_bool bHasNewData;
static SDL_bool bHasEnvironmentVariables; static SDL_bool bHasEnvironmentVariables;
static SDL_AtomicInt bPermissionRequestPending;
static SDL_bool bPermissionRequestResult;
/* Android AssetManager */ /* Android AssetManager */
static void Internal_Android_Create_AssetManager(void); static void Internal_Android_Create_AssetManager(void);
static void Internal_Android_Destroy_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); (*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 JNIEXPORT void JNICALL
SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
jstring name, jint device_id) 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)); return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
} }
SDL_bool Android_JNI_RequestPermission(const char *permission) typedef struct NativePermissionRequestInfo
{ {
JNIEnv *env = Android_JNI_GetEnv(); int request_code;
jstring jpermission; char *permission;
const int requestCode = 1; SDL_AndroidRequestPermissionCallback callback;
void *userdata;
struct NativePermissionRequestInfo *next;
} NativePermissionRequestInfo;
/* Wait for any pending request on another thread */ static NativePermissionRequestInfo pending_permissions;
while (SDL_AtomicGet(&bPermissionRequestPending) == SDL_TRUE) {
SDL_Delay(10); 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); SDL_UnlockMutex(Android_ActivityMutex);
(*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, requestCode); 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); (*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 */ /* Wait for the request to complete */
while (SDL_AtomicGet(&bPermissionRequestPending) == SDL_TRUE) { while (SDL_AtomicGet(&response) == 0) {
SDL_Delay(10); SDL_Delay(10);
} }
return bPermissionRequestResult;
return (SDL_AtomicGet(&response) < 0) ? SDL_FALSE : SDL_TRUE;
} }
/* Show toast notification */ /* Show toast notification */
int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset) int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset)
{ {

View file

@ -972,6 +972,7 @@ SDL3_0.0.0 {
SDL_RenderGeometryRawFloat; SDL_RenderGeometryRawFloat;
SDL_SetWindowShape; SDL_SetWindowShape;
SDL_RenderViewportSet; SDL_RenderViewportSet;
SDL_AndroidRequestPermissionAsync;
# extra symbols go here (don't modify this line) # extra symbols go here (don't modify this line)
local: *; local: *;
}; };

View file

@ -997,3 +997,4 @@
#define SDL_RenderGeometryRawFloat SDL_RenderGeometryRawFloat_REAL #define SDL_RenderGeometryRawFloat SDL_RenderGeometryRawFloat_REAL
#define SDL_SetWindowShape SDL_SetWindowShape_REAL #define SDL_SetWindowShape SDL_SetWindowShape_REAL
#define SDL_RenderViewportSet SDL_RenderViewportSet_REAL #define SDL_RenderViewportSet SDL_RenderViewportSet_REAL
#define SDL_AndroidRequestPermissionAsync SDL_AndroidRequestPermissionAsync_REAL

View file

@ -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_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(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(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)

View file

@ -45,4 +45,8 @@ typedef int SDL_WinRT_Path;
typedef struct XUserHandle XUserHandle; typedef struct XUserHandle XUserHandle;
#endif #endif
#ifndef SDL_PLATFORM_ANDROID
typedef void *SDL_AndroidRequestPermissionCallback;
#endif
#endif #endif