SDL: Android: add command mecanism so that SDLActivity executes no SDL code,

From 8db38014fc8f4354c407a942d9d6ba33eeb4302c Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Mon, 2 Feb 2026 14:50:26 +0100
Subject: [PATCH] Android: add command mecanism so that SDLActivity executes no
 SDL code,     but defers it to SDL main thread

---
 src/core/android/SDL_android.c           | 1484 +++++++++++++++++-----
 src/core/android/SDL_android.h           |    8 +-
 src/events/SDL_events.c                  |    9 +-
 src/joystick/android/SDL_sysjoystick.c   |    6 +-
 src/joystick/android/SDL_sysjoystick_c.h |    1 +
 src/render/SDL_render.c                  |    6 +-
 src/video/android/SDL_androidevents.c    |  125 +-
 src/video/android/SDL_androidevents.h    |    6 +-
 src/video/android/SDL_androidgl.c        |    8 +-
 src/video/android/SDL_androidvideo.c     |    6 +-
 src/video/android/SDL_androidvideo.h     |    2 +-
 src/video/android/SDL_androidwindow.c    |  200 +--
 src/video/android/SDL_androidwindow.h    |    8 +-
 13 files changed, 1383 insertions(+), 486 deletions(-)

diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 62bb6602717f0..a6a9d562929ab 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -31,6 +31,7 @@
 #include "../../video/android/SDL_androidpen.h"
 #include "../../video/android/SDL_androidvideo.h"
 #include "../../video/android/SDL_androidwindow.h"
+#include "../../video/android/SDL_androidevents.h"
 #include "../../joystick/android/SDL_sysjoystick_c.h"
 #include "../../haptic/android/SDL_syshaptic_c.h"
 #include "../../hidapi/android/hid.h"
@@ -53,11 +54,6 @@
 #define SDL_JAVA_CONTROLLER_INTERFACE(function)       CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
 #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
 
-// Audio encoding definitions
-#define ENCODING_PCM_8BIT  3
-#define ENCODING_PCM_16BIT 2
-#define ENCODING_PCM_FLOAT 4
-
 // Java class SDLActivity
 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)(
     JNIEnv *env, jclass cls);
@@ -342,6 +338,7 @@ static JNINativeMethod SDLControllerManager_tab[] = {
 
 // Uncomment this to log messages entering and exiting methods in this file
 // #define DEBUG_JNI
+// #define DEBUG_RPC
 
 static void checkJNIReady(void);
 
@@ -426,12 +423,187 @@ static void Internal_Android_Destroy_AssetManager(void);
 static AAssetManager *asset_manager = NULL;
 static jobject javaAssetManagerRef = 0;
 
-static SDL_Mutex *Android_ActivityMutex = NULL;
 static SDL_Mutex *Android_LifecycleMutex = NULL;
 static SDL_Semaphore *Android_LifecycleEventSem = NULL;
 static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS];
 static int Android_NumLifecycleEvents;
 
+// RPC commands. from SDLActivity thread to C Thread
+typedef enum {
+
+    // SDLActivity_tab
+
+    // RPC_cmd_nativeGetVersion,
+    // RPC_cmd_nativeInitMainThread,
+    // RPC_cmd_nativeCleanupMainThread,
+    // RPC_cmd_nativeRunMain,
+    RPC_cmd_onNativeDropFile,
+    RPC_cmd_nativeSetScreenResolution,
+    RPC_cmd_onNativeResize,
+    RPC_cmd_onNativeSurfaceCreated,
+    RPC_cmd_onNativeSurfaceChanged,
+    RPC_cmd_onNativeSurfaceDestroyed,
+    RPC_cmd_onNativeScreenKeyboardShown,
+    RPC_cmd_onNativeScreenKeyboardHidden,
+    RPC_cmd_onNativeKeyDown,
+    RPC_cmd_onNativeKeyUp,
+    RPC_cmd_onNativeSoftReturnKey,
+    RPC_cmd_onNativeKeyboardFocusLost,
+    RPC_cmd_onNativeTouch,
+    RPC_cmd_onNativePinchStart,
+    RPC_cmd_onNativePinchUpdate,
+    RPC_cmd_onNativePinchEnd,
+    RPC_cmd_onNativeMouse,
+    RPC_cmd_onNativePen,
+    RPC_cmd_onNativeAccel,
+    RPC_cmd_onNativeClipboardChanged,
+// lifecycle event
+//    RPC_cmd_nativeLowMemory,
+    RPC_cmd_onNativeLocaleChanged,
+    RPC_cmd_onNativeDarkModeChanged,
+// lifecycle event
+//    RPC_cmd_nativeSendQuit,
+// onDestroy / inverse of nativeSetupJNI
+//    RPC_cmd_nativeQuit,
+// lifecycle event
+//    RPC_cmd_nativePause,
+//    RPC_cmd_nativeResume,
+    RPC_cmd_nativeFocusChanged,
+// there are not returned void and so, they cannot really be defered to C Thread:
+//    RPC_cmd_nativeGetHint,
+//    RPC_cmd_nativeGetHintBoolean,
+
+// special case with recreate activity:
+//    RPC_cmd_nativeSetenv,
+//
+    RPC_cmd_nativeSetNaturalOrientation,
+    RPC_cmd_onNativeRotationChanged,
+    RPC_cmd_onNativeInsetsChanged,
+    RPC_cmd_nativeAddTouch,
+    RPC_cmd_nativePermissionResult,
+
+// only used within the SDLActivity thread:
+//    RPC_cmd_nativeAllowRecreateActivity,
+//    RPC_cmd_nativeCheckSDLThreadCounter,
+    RPC_cmd_onNativeFileDialog,
+
+    // SDLInputConnection_tab
+    RPC_cmd_nativeCommitText,
+    RPC_cmd_nativeGenerateScancodeForUnichar,
+
+    // SDLAudioManager_tab
+    RPC_cmd_nativeAddAudioDevice,
+    RPC_cmd_nativeRemoveAudioDevice,
+
+    // SDLControllerManager_tab
+    RPC_cmd_onNativePadDown,
+    RPC_cmd_onNativePadUp,
+    RPC_cmd_onNativeJoy,
+    RPC_cmd_onNativeHat,
+    RPC_cmd_nativeAddJoystick,
+    RPC_cmd_nativeRemoveJoystick,
+    RPC_cmd_nativeAddHaptic,
+    RPC_cmd_nativeRemoveHaptic
+
+    // RPC TODO HID ? see HIDDeviceManager_tab
+
+} RPC_cmd_t;
+
+
+#ifdef DEBUG_RPC
+
+static const char *cmd2Str(RPC_cmd_t cmd) {
+    switch (cmd) {
+#define CASE(x)     case RPC_cmd_ ## x: return #x;
+    // SDLActivity_tab
+    CASE(onNativeDropFile);
+    CASE(nativeSetScreenResolution);
+    CASE(onNativeResize);
+    CASE(onNativeSurfaceCreated);
+    CASE(onNativeSurfaceChanged);
+    CASE(onNativeSurfaceDestroyed);
+    CASE(onNativeScreenKeyboardShown);
+    CASE(onNativeScreenKeyboardHidden);
+    CASE(onNativeKeyDown);
+    CASE(onNativeKeyUp);
+    CASE(onNativeSoftReturnKey);
+    CASE(onNativeKeyboardFocusLost);
+    CASE(onNativeTouch);
+    CASE(onNativePinchStart);
+    CASE(onNativePinchUpdate);
+    CASE(onNativePinchEnd);
+    CASE(onNativeMouse);
+    CASE(onNativePen);
+    CASE(onNativeAccel);
+    CASE(onNativeClipboardChanged);
+    CASE(onNativeLocaleChanged);
+    CASE(onNativeDarkModeChanged);
+    CASE(nativeFocusChanged);
+    CASE(nativeSetNaturalOrientation);
+    CASE(onNativeRotationChanged);
+    CASE(onNativeInsetsChanged);
+    CASE(nativeAddTouch);
+    CASE(nativePermissionResult);
+    CASE(onNativeFileDialog);
+
+    // SDLInputConnection_tab
+    CASE(nativeCommitText);
+    CASE(nativeGenerateScancodeForUnichar);
+
+    // SDLAudioManager_tab
+    CASE(nativeAddAudioDevice);
+    CASE(nativeRemoveAudioDevice);
+
+    // SDLControllerManager_tab
+    CASE(onNativePadDown);
+    CASE(onNativePadUp);
+    CASE(onNativeJoy);
+    CASE(onNativeHat);
+    CASE(nativeAddJoystick);
+    CASE(nativeRemoveJoystick);
+    CASE(nativeAddHaptic);
+    CASE(nativeRemoveHaptic);
+
+#undef CASE
+        default:
+            return "unknown";
+    }
+}
+#endif
+
+
+// RPC TODO: enable timestamp ?
+#define RPC_Send                                \
+    /* data.timestamp = SDL_GetTicks(); */      \
+    RPC_Send__(&data, sizeof(data));            \
+
+#define RPC_Add(foo)    data.foo = foo;
+
+#define RPC_AddString(foo)                                              \
+    const char *utf##foo = (*env)->GetStringUTFChars(env, foo, NULL);   \
+    data.foo = SDL_strdup(utf##foo);                                    \
+    (*env)->ReleaseStringUTFChars(env, foo, utf##foo);                  \
+
+#define RPC_Prepare(foo)                \
+    RPC_data_##foo##_t data;            \
+    data.cmd = RPC_cmd_##foo;           \
+
+#define RPC_SendWithoutData(foo)        \
+    RPC_data_t data;                    \
+    data.cmd = RPC_cmd_##foo;           \
+    data.timestamp = SDL_GetTicks();    \
+    RPC_Send__(&data, sizeof(data));    \
+
+static void RPC_Send__(void *data, int len);
+static void RPC_Init();
+
+// header for command without data needed
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+} RPC_data_t;
+
+
 /*******************************************************************************
                  Functions called by JNI
 *******************************************************************************/
@@ -615,6 +787,10 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
     // Start with a clean slate
     SDL_ClearError();
 
+
+    RPC_Init();
+
+
     /*
      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
@@ -628,17 +804,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM");
     }
 
-    /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'.
-     * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. )
-     */
-    if (!Android_ActivityMutex) {
-        Android_ActivityMutex = SDL_CreateMutex(); // Could this be created twice if onCreate() is called a second time ?
-    }
-
-    if (!Android_ActivityMutex) {
-        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex");
-    }
-
     Android_LifecycleMutex = SDL_CreateMutex();
     if (!Android_LifecycleMutex) {
         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleMutex mutex");
@@ -1008,188 +1173,296 @@ bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeout
     return got_event;
 }
 
-void Android_LockActivityMutex(void)
+void Android_LockActivityState(void)
 {
-    SDL_LockMutex(Android_ActivityMutex);
+    SDL_LockMutex(Android_LifecycleMutex);
 }
 
-void Android_UnlockActivityMutex(void)
+void Android_UnlockActivityState(void)
 {
-    SDL_UnlockMutex(Android_ActivityMutex);
+    SDL_UnlockMutex(Android_LifecycleMutex);
 }
 
 // Drop file
+
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    char *filename;
+} RPC_data_onNativeDropFile_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
     JNIEnv *env, jclass jcls,
     jstring filename)
 {
-    const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
-    SDL_SendDropFile(NULL, NULL, path);
-    (*env)->ReleaseStringUTFChars(env, filename, path);
-    SDL_SendDropComplete(NULL);
+    RPC_Prepare(onNativeDropFile);
+    RPC_AddString(filename);
+    RPC_Send;
 }
 
 // Set screen resolution
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int surfaceWidth;
+    int surfaceHeight;
+    int deviceWidth;
+    int deviceHeight;
+    float density;
+    float rate;
+} RPC_data_nativeSetScreenResolution_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)(
     JNIEnv *env, jclass jcls,
     jint surfaceWidth, jint surfaceHeight,
     jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, density, rate);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(nativeSetScreenResolution);
+    RPC_Add(surfaceWidth);
+    RPC_Add(surfaceHeight);
+    RPC_Add(deviceWidth);
+    RPC_Add(deviceHeight);
+    RPC_Add(density);
+    RPC_Add(rate);
+    RPC_Send;
 }
 
 // Resize
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
     JNIEnv *env, jclass jcls)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        Android_SendResize(Android_Window);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativeResize);
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int orientation;
+} RPC_data_nativeSetNaturalOrientation_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)(
     JNIEnv *env, jclass jcls,
     jint orientation)
 {
-    displayNaturalOrientation = (SDL_DisplayOrientation)orientation;
+    RPC_Prepare(nativeSetNaturalOrientation);
+    RPC_Add(orientation);
+    RPC_Send;
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int rotation;
+} RPC_data_onNativeRotationChanged_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)(
     JNIEnv *env, jclass jcls,
     jint rotation)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) {
-        rotation += 90;
-    }
-
-    switch (rotation % 360) {
-    case 0:
-        displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT;
-        break;
-    case 90:
-        displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE;
-        break;
-    case 180:
-        displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
-        break;
-    case 270:
-        displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
-        break;
-    default:
-        displayCurrentOrientation = SDL_ORIENTATION_UNKNOWN;
-        break;
-    }
+    RPC_Prepare(onNativeRotationChanged);
+    RPC_Add(rotation);
+    RPC_Send;
+}
 
-    Android_SetOrientation(displayCurrentOrientation);
 
-    SDL_UnlockMutex(Android_ActivityMutex);
-}
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int left;
+    int right;
+    int top;
+    int bottom;
+} RPC_data_onNativeInsetsChanged_t;
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
     JNIEnv *env, jclass jcls,
     jint left, jint right, jint top, jint bottom)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    Android_SetWindowSafeAreaInsets(left, right, top, bottom);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeInsetsChanged);
+    RPC_Add(left);
+    RPC_Add(right);
+    RPC_Add(top);
+    RPC_Add(bottom);
+    RPC_Send;
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int touchId;
+    char *name;
+} RPC_data_nativeAddTouch_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
     JNIEnv *env, jclass cls,
     jint touchId, jstring name)
 {
-    const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
+    RPC_Prepare(nativeAddTouch);
+    RPC_Add(touchId);
+    RPC_AddString(name);
+    RPC_Send;
+}
 
-    SDL_AddTouch(Android_ConvertJavaTouchID(touchId),
-            SDL_TOUCH_DEVICE_DIRECT, utfname);
 
-    (*env)->ReleaseStringUTFChars(env, name, utfname);
-}
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    bool recording;
+    char *name;
+    int device_id;
+} RPC_data_nativeAddAudioDevice_t;
+
 
 JNIEXPORT void JNICALL
 SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
                                          jstring name, jint device_id)
 {
 #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
-    if (SDL_GetCurrentAudioDriver() != NULL) {
-        void *handle = (void *)((size_t)device_id);
-        if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) {
-            const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL);
-            SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle);
-            (*env)->ReleaseStringUTFChars(env, name, utf8name);
-        }
-    }
+    RPC_Prepare(nativeAddAudioDevice);
+    RPC_Add(recording);
+    RPC_AddString(name);
+    RPC_Add(device_id);
+    RPC_Send;
 #endif
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    bool recording;
+    int device_id;
+} RPC_data_nativeRemoveAudioDevice_t;
+
 JNIEXPORT void JNICALL
 SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
                                             jint device_id)
 {
 #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
-    if (SDL_GetCurrentAudioDriver() != NULL) {
-        SDL_Log("Removing device with handle %d, recording %d", device_id, recording);
-        SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id)));
-    }
+    RPC_Prepare(nativeRemoveAudioDevice);
+    RPC_Add(recording);
+    RPC_Add(device_id);
+    RPC_Send;
 #endif
 }
 
 // Paddown
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+    int keycode;
+} RPC_data_onNativePadDown_t;
+
 JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
     JNIEnv *env, jclass jcls,
     jint device_id, jint keycode)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    return Android_OnPadDown(device_id, keycode);
+int button = android_keycode_to_SDL(keycode);
+    if (button >= 0) {
+        RPC_Prepare(onNativePadDown);
+        RPC_Add(device_id);
+        RPC_Add(keycode);
+        RPC_Send;
+        return true;
+    }
+    return false;
 #else
     return false;
 #endif // SDL_JOYSTICK_ANDROID
 }
 
 // Padup
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+int device_id;
+    int keycode;
+} RPC_data_onNativePadUp_t;
+
 JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
     JNIEnv *env, jclass jcls,
     jint device_id, jint keycode)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    return Android_OnPadUp(device_id, keycode);
+    int button = android_keycode_to_SDL(keycode);
+    if (button >= 0) {
+        RPC_Prepare(onNativePadUp);
+        RPC_Add(device_id);
+        RPC_Add(keycode);
+        RPC_Send;
+        return true;
+    }
+    return false;
 #else
     return false;
 #endif // SDL_JOYSTICK_ANDROID
 }
 
+
 // Joy
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+    int axis;
+    float value;
+} RPC_data_onNativeJoy_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
     JNIEnv *env, jclass jcls,
     jint device_id, jint axis, jfloat value)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    Android_OnJoy(device_id, axis, value);
-#endif // SDL_JOYSTICK_ANDROID
+    RPC_Prepare(onNativeJoy);
+    RPC_Add(device_id);
+    RPC_Add(axis);
+    RPC_Add(value);
+    RPC_Send;
+#endif
 }
 
 // POV Hat
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+    int hat_id;
+    int x;
+    int y;
+} RPC_data_onNativeHat_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
     JNIEnv *env, jclass jcls,
     jint device_id, jint hat_id, jint x, jint y)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    Android_OnHat(device_id, hat_id, x, y);
-#endif // SDL_JOYSTICK_ANDROID
+    RPC_Prepare(onNativeHat);
+    RPC_Add(device_id);
+    RPC_Add(hat_id);
+    RPC_Add(x);
+    RPC_Add(y);
+    RPC_Send;
+#endif
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+    char *device_name;
+    char *device_desc;
+    int vendor_id;
+    int product_id;
+    int button_mask;
+    int naxes;
+    int axis_mask;
+    int nhats;
+    bool can_rumble;
+    bool has_rgb_led;
+} RPC_data_nativeAddJoystick_t;
+
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
     JNIEnv *env, jclass jcls,
     jint device_id, jstring device_name, jstring device_desc,
@@ -1197,168 +1470,132 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
     jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble, jboolean has_rgb_led)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
-    const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
-
-    Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble, has_rgb_led);
-
-    (*env)->ReleaseStringUTFChars(env, device_name, name);
-    (*env)->ReleaseStringUTFChars(env, device_desc, desc);
-#endif // SDL_JOYSTICK_ANDROID
+    RPC_Prepare(nativeAddJoystick);
+    RPC_Add(device_id);
+    RPC_AddString(device_name);
+    RPC_AddString(device_desc);
+    RPC_Add(vendor_id);
+    RPC_Add(product_id);
+    RPC_Add(button_mask);
+    RPC_Add(naxes);
+    RPC_Add(axis_mask);
+    RPC_Add(nhats);
+    RPC_Add(can_rumble);
+    RPC_Add(has_rgb_led);
+    RPC_Send;
+#endif
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+} RPC_data_nativeRemoveJoystick_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
     JNIEnv *env, jclass jcls,
     jint device_id)
 {
 #ifdef SDL_JOYSTICK_ANDROID
-    Android_RemoveJoystick(device_id);
-#endif // SDL_JOYSTICK_ANDROID
+    RPC_Prepare(nativeRemoveJoystick);
+    RPC_Add(device_id);
+    RPC_Send;
+#endif
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+    char *device_name;
+} RPC_data_nativeAddHaptic_t;
+
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
     JNIEnv *env, jclass jcls, jint device_id, jstring device_name)
 {
 #ifdef SDL_HAPTIC_ANDROID
-    const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
-
-    Android_AddHaptic(device_id, name);
-
-    (*env)->ReleaseStringUTFChars(env, device_name, name);
-#endif // SDL_HAPTIC_ANDROID
+    RPC_Prepare(nativeAddHaptic);
+    RPC_Add(device_name);
+    RPC_AddString(device_name);
+    RPC_Send;
+#endif
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int device_id;
+} RPC_data_nativeRemoveHaptic_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
     JNIEnv *env, jclass jcls, jint device_id)
 {
 #ifdef SDL_HAPTIC_ANDROID
-    Android_RemoveHaptic(device_id);
+    RPC_Prepare(nativeRemoveHaptic);
+    RPC_Add(device_id);
+    RPC_Send;
 #endif
 }
 
 // Called from surfaceCreated()
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        SDL_WindowData *data = Android_Window->internal;
-
-        data->native_window = Android_JNI_GetNativeWindow();
-        SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
-        if (data->native_window == NULL) {
-            SDL_SetError("Could not fetch native window from UI thread");
-        }
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativeSurfaceCreated);
 }
 
 // Called from surfaceChanged()
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, jclass jcls)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-#ifdef SDL_VIDEO_OPENGL_EGL
-    if (Android_Window && (Android_Window->flags & SDL_WINDOW_OPENGL)) {
-        SDL_VideoDevice *_this = SDL_GetVideoDevice();
-        SDL_WindowData *data = Android_Window->internal;
-
-        // If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here
-        if (data->egl_surface == EGL_NO_SURFACE) {
-            data->egl_surface = SDL_EGL_CreateSurface(_this, Android_Window, (NativeWindowType)data->native_window);
-            SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
-        }
-
-        // GL Context handling is done in the event loop because this function is run from the Java thread
-    }
-#endif
-
-    if (Android_Window) {
-        Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), Android_Window);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativeSurfaceChanged);
 }
 
 // Called from surfaceDestroyed()
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv *env, jclass jcls)
 {
-    int nb_attempt = 50;
-
-retry:
-
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        SDL_WindowData *data = Android_Window->internal;
-
-        // Wait for Main thread being paused and context un-activated to release 'egl_surface'
-        if ((Android_Window->flags & SDL_WINDOW_OPENGL) && !data->backup_done) {
-            nb_attempt -= 1;
-            if (nb_attempt == 0) {
-                SDL_SetError("Try to release egl_surface with context probably still active");
-            } else {
-                SDL_UnlockMutex(Android_ActivityMutex);
-                SDL_Delay(10);
-                goto retry;
-            }
-        }
-
-#ifdef SDL_VIDEO_OPENGL_EGL
-        if (data->egl_surface != EGL_NO_SURFACE) {
-            SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface);
-            data->egl_surface = EGL_NO_SURFACE;
-        }
-#endif
-
-        if (data->native_window) {
-            ANativeWindow_release(data->native_window);
-            data->native_window = NULL;
-        }
-
-        // GL Context handling is done in the event loop because this function is run from the Java thread
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativeSurfaceDestroyed);
 }
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardShown)(JNIEnv *env, jclass jcls)
 {
-    SDL_SendScreenKeyboardShown();
+    RPC_SendWithoutData(onNativeScreenKeyboardShown);
 }
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeScreenKeyboardHidden)(JNIEnv *env, jclass jcls)
 {
-    SDL_SendScreenKeyboardHidden();
+    RPC_SendWithoutData(onNativeScreenKeyboardHidden);
 }
 
 // Keydown
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int keycode;
+} RPC_data_onNativeKeyDown_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
     JNIEnv *env, jclass jcls,
     jint keycode)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        Android_OnKeyDown(keycode);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeKeyDown);
+    RPC_Add(keycode);
+    RPC_Send;
 }
 
 // Keyup
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int keycode;
+} RPC_data_onNativeKeyUp_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
     JNIEnv *env, jclass jcls,
     jint keycode)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        Android_OnKeyUp(keycode);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeKeyUp);
+    RPC_Add(keycode);
+    RPC_Send;
 }
 
 // Virtual keyboard return key might stop text input
@@ -1366,7 +1603,9 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
     JNIEnv *env, jclass jcls)
 {
     if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
-        SDL_StopTextInput(Android_Window);
+
+        RPC_SendWithoutData(onNativeSoftReturnKey);
+
         return JNI_TRUE;
     }
     return JNI_FALSE;
@@ -1376,101 +1615,140 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
     JNIEnv *env, jclass jcls)
 {
-    // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget
-    SDL_StopTextInput(Android_Window);
+    RPC_SendWithoutData(onNativeKeyboardFocusLost);
 }
 
 // Touch
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int touch_device_id_in;
+    int pointer_finger_id_in;
+    int action;
+    float x;
+    float y;
+    float p;
+} RPC_data_onNativeTouch_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
     JNIEnv *env, jclass jcls,
     jint touch_device_id_in, jint pointer_finger_id_in,
     jint action, jfloat x, jfloat y, jfloat p)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    Android_OnTouch(Android_Window, touch_device_id_in, pointer_finger_id_in, action, x, y, p);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeTouch);
+    RPC_Add(touch_device_id_in);
+    RPC_Add(pointer_finger_id_in);
+    RPC_Add(action);
+    RPC_Add(x);
+    RPC_Add(y);
+    RPC_Add(p);
+    RPC_Send;
 }
 
 // Pinch
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)(
     JNIEnv *env, jclass jcls)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativePinchStart);
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    float scale;
+} RPC_data_onNativePinchUpdate_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)(
     JNIEnv *env, jclass jcls, jfloat scale)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativePinchUpdate);
+    RPC_Add(scale);
+    RPC_Send;
 }
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)(
     JNIEnv *env, jclass jcls)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_SendWithoutData(onNativePinchEnd);
 }
 
 // Mouse
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int button;
+    int action;
+    float x;
+    float y;
+    bool relative;
+} RPC_data_onNativeMouse_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
     JNIEnv *env, jclass jcls,
     jint button, jint action, jfloat x, jfloat y, jboolean relative)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    Android_OnMouse(Android_Window, button, action, x, y, relative);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeMouse);
+    RPC_Add(button);
+    RPC_Add(action);
+    RPC_Add(x);
+    RPC_Add(y);
+    RPC_Add(relative);
+    RPC_Send;
 }
 
 // Pen
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int pen_id_in;
+    int device_type;
+    int button;
+    int action;
+    float x;
+    float y;
+    float p;
+} RPC_data_onNativePen_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)(
     JNIEnv *env, jclass jcls,
     jint pen_id_in, jint device_type, jint button, jint action, jfloat x, jfloat y, jfloat p)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    Android_OnPen(Android_Window, pen_id_in, device_type, button, action, x, y, p);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativePen);
+    RPC_Add(pen_id_in);
+    RPC_Add(device_type);
+    RPC_Add(button);
+    RPC_Add(action);
+    RPC_Add(x);
+    RPC_Add(y);
+    RPC_Add(p);
+    RPC_Send;
 }
 
 // Accelerometer
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    float x;
+    float y;
+    float z;
+} RPC_data_onNativeAccel_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
     JNIEnv *env, jclass jcls,
     jfloat x, jfloat y, jfloat z)
 {
-    fLastAccelerometer[0] = x;
-    fLastAccelerometer[1] = y;
-    fLastAccelerometer[2] = z;
-    bHasNewData = true;
+    RPC_Prepare(onNativeAccel);
+    RPC_Add(x);
+    RPC_Add(y);
+    RPC_Add(z);
+    RPC_Send;
 }
 
 // Clipboard
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
     JNIEnv *env, jclass jcls)
 {
-    // TODO: compute new mime types
-    SDL_SendClipboardUpdate(false, NULL, 0);
+    RPC_SendWithoutData(onNativeClipboardChanged);
 }
 
 // Low memory
@@ -1485,14 +1763,22 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
     JNIEnv *env, jclass cls)
 {
-    SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED);
+    RPC_SendWithoutData(onNativeLocaleChanged);
 }
 
 // Dark mode
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    bool enabled;
+} RPC_data_onNativeDarkModeChanged_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
     JNIEnv *env, jclass cls, jboolean enabled)
 {
-    Android_SetDarkMode(enabled);
+    R

(Patch may be truncated, please check the link at the top of this post.)