Android: add command mecanism so that SDLActivity executes no SDL code,
    but defers it to SDL main thread
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 62bb660..a6a9d56 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 @@
 
 // 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 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 @@
     // 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 @@
         __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 @@
     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;
-    }
-
-    Android_SetOrientation(displayCurrentOrientation);
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(onNativeRotationChanged);
+    RPC_Add(rotation);
+    RPC_Send;
 }
 
+
+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);
-
-    SDL_AddTouch(Android_ConvertJavaTouchID(touchId),
-            SDL_TOUCH_DEVICE_DIRECT, utfname);
-
-    (*env)->ReleaseStringUTFChars(env, name, utfname);
+    RPC_Prepare(nativeAddTouch);
+    RPC_Add(touchId);
+    RPC_AddString(name);
+    RPC_Send;
 }
 
+
+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 @@
     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 @@
     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 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(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);
+    RPC_Prepare(onNativeDarkModeChanged);
+    RPC_Add(enabled);
+    RPC_Send;
 }
 
 // Send Quit event to "SDLThread" thread
@@ -1508,11 +1794,6 @@
 {
     const char *str;
 
-    if (Android_ActivityMutex) {
-        SDL_DestroyMutex(Android_ActivityMutex);
-        Android_ActivityMutex = NULL;
-    }
-
     if (Android_LifecycleMutex) {
         SDL_DestroyMutex(Android_LifecycleMutex);
         Android_LifecycleMutex = NULL;
@@ -1553,35 +1834,52 @@
     Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME);
 }
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    bool hasFocus;
+} RPC_data_nativeFocusChanged_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
     JNIEnv *env, jclass cls, jboolean hasFocus)
 {
-    SDL_LockMutex(Android_ActivityMutex);
-
-    if (Android_Window) {
-        __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()");
-        SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0);
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(nativeFocusChanged);
+    RPC_Add(hasFocus);
+    RPC_Send;
 }
 
+
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    char *text;
+    int newCursorPosition;
+} RPC_data_nativeCommitText_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
     JNIEnv *env, jclass cls,
     jstring text, jint newCursorPosition)
 {
-    const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
-
-    SDL_SendKeyboardText(utftext);
-
-    (*env)->ReleaseStringUTFChars(env, text, utftext);
+    RPC_Prepare(nativeCommitText);
+    RPC_AddString(text);
+    RPC_Add(newCursorPosition);
+    RPC_Send;
 }
 
+
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    char chUnicode;
+} RPC_data_nativeGenerateScancodeForUnichar_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
     JNIEnv *env, jclass cls,
     jchar chUnicode)
 {
-    SDL_SendKeyboardUnicodeKey(0, chUnicode);
+    RPC_Prepare(nativeGenerateScancodeForUnichar);
+    RPC_Add(chUnicode);
+    RPC_Send;
 }
 
 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
@@ -2683,25 +2981,21 @@
 
 static NativePermissionRequestInfo pending_permissions;
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+    int requestCode;
+    bool result;
+} RPC_data_nativePermissionResult_t;
+
 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 ? true : false);
-            SDL_free(info->permission);
-            SDL_free(info);
-            return;
-        }
-        prev = info;
-    }
-
-    SDL_UnlockMutex(Android_ActivityMutex);
+    RPC_Prepare(nativePermissionResult);
+    RPC_Add(requestCode);
+    RPC_Add(result);
+    RPC_Send;
 }
 
 bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata)
@@ -2729,10 +3023,8 @@
     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);
@@ -2844,73 +3136,60 @@
     void *userdata;
 } mAndroidFileDialogData;
 
+typedef struct {
+    RPC_cmd_t cmd;
+    Uint64 timestamp;
+
+    int requestCode;
+    int filter;
+
+    char **charFileList;
+    size_t count;
+
+} RPC_data_onNativeFileDialog_t;
+
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)(
     JNIEnv *env, jclass jcls,
     jint requestCode, jobjectArray fileList, jint filter)
 {
-    if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == requestCode) {
-        if (fileList == NULL) {
-            SDL_SetError("Unspecified error in JNI");
-            mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
-            mAndroidFileDialogData.callback = NULL;
-            return;
-        }
+    size_t count = 0;
+    char **charFileList = NULL;
 
-        // Convert fileList to string
-        size_t count = (*env)->GetArrayLength(env, fileList);
-        char **charFileList = SDL_calloc(count + 1, sizeof(char *));
+    if (fileList) {
+        count = (*env)->GetArrayLength(env, fileList);
+        charFileList = SDL_calloc(count + 1, sizeof(char *));
 
-        if (charFileList == NULL) {
-            mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
-            mAndroidFileDialogData.callback = NULL;
-            return;
-        }
+        if (charFileList) {
+            // Convert to UTF-8
+            // TODO: Fix modified UTF-8 to classic UTF-8
+            for (int i = 0; i < count; i++) {
+                jstring string = (*env)->GetObjectArrayElement(env, fileList, i);
+                if (!string) {
+                    continue;
+                }
 
-        // Convert to UTF-8
-        // TODO: Fix modified UTF-8 to classic UTF-8
-        for (int i = 0; i < count; i++) {
-            jstring string = (*env)->GetObjectArrayElement(env, fileList, i);
-            if (!string) {
-                continue;
-            }
+                const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL);
+                if (!utf8string) {
+                    (*env)->DeleteLocalRef(env, string);
+                    continue;
+                }
 
-            const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL);
-            if (!utf8string) {
-                (*env)->DeleteLocalRef(env, string);
-                continue;
-            }
-
-            char *newFile = SDL_strdup(utf8string);
-            if (!newFile) {
+                char *newFile = SDL_strdup(utf8string);
+                charFileList[i] = newFile;
                 (*env)->ReleaseStringUTFChars(env, string, utf8string);
                 (*env)->DeleteLocalRef(env, string);
-                mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
-                mAndroidFileDialogData.callback = NULL;
-
-                // Cleanup memory
-                for (int j = 0; j < i; j++) {
-                    SDL_free(charFileList[j]);
-                }
-                SDL_free(charFileList);
-                return;
             }
-
-            charFileList[i] = newFile;
-            (*env)->ReleaseStringUTFChars(env, string, utf8string);
-            (*env)->DeleteLocalRef(env, string);
         }
-
-        // Call user-provided callback
-        SDL_ClearError();
-        mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) charFileList, filter);
-        mAndroidFileDialogData.callback = NULL;
-
-        // Cleanup memory
-        for (int i = 0; i < count; i++) {
-            SDL_free(charFileList[i]);
-        }
-        SDL_free(charFileList);
+    } else {
+        SDL_SetError("Unspecified error in JNI");
     }
+
+    RPC_Prepare(onNativeFileDialog);
+    RPC_Add(requestCode);
+    RPC_Add(filter);
+    RPC_Add(count);
+    RPC_Add(charFileList);
+    RPC_Send;
 }
 
 bool Android_JNI_OpenFileDialog(
@@ -2965,4 +3244,545 @@
     return true;
 }
 
+
+#define RPC_PAGE_SIZE (1024 * 10)
+static char RPC_cmd_buffer_0[RPC_PAGE_SIZE];
+static char RPC_cmd_buffer_1[RPC_PAGE_SIZE];
+
+static char *RPC_cmd_buffer = NULL;  // RPC_cmd_buffer_0 or RPC_cmd_buffer_1
+static int  RPC_cur_cmd_buffer = 0;  // 0 or 1
+static int  RPC_cur_size = 0;        // used size
+static int  RPC_cur_nb_cmd = 0;      // nb of cmds
+
+static SDL_Mutex *RPC_Mutex = NULL;
+
+
+static void RPC_Init()
+{
+    RPC_cmd_buffer = RPC_cmd_buffer_0;
+    RPC_cur_cmd_buffer = 0;
+    RPC_cur_size =  0;
+    RPC_cur_nb_cmd = 0;
+
+    if (!RPC_Mutex) {
+        RPC_Mutex = SDL_CreateMutex();
+    }
+
+    if (!RPC_Mutex) {
+        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create RPC_Mutex mutex");
+    }
+
+}
+
+static void RPC_Send__(void *data, int len)
+{
+    SDL_LockMutex(RPC_Mutex);
+
+    if (RPC_cur_size + len < RPC_PAGE_SIZE) {
+        SDL_memcpy(RPC_cmd_buffer + RPC_cur_size, data, len);
+        RPC_cur_size += len;
+        RPC_cur_nb_cmd += 1;
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, "SDL", "cannot add RPC of len %d", len);
+    }
+
+    SDL_UnlockMutex(RPC_Mutex);
+}
+
+void Android_PumpRPC(SDL_Window *window)
+{
+    int nb_cmd = 0;
+    char *cmd_buffer = NULL;
+
+    SDL_LockMutex(RPC_Mutex);
+
+    if (RPC_cur_nb_cmd) {
+        cmd_buffer = RPC_cmd_buffer;
+        nb_cmd = RPC_cur_nb_cmd;
+
+        // Toggle cmd buffer
+        RPC_cur_size = 0;
+        RPC_cur_nb_cmd = 0;
+
+        if (RPC_cur_cmd_buffer == 0) {
+            RPC_cur_cmd_buffer = 1;
+            RPC_cmd_buffer = RPC_cmd_buffer_1;
+        } else {
+            RPC_cur_cmd_buffer = 0;
+            RPC_cmd_buffer = RPC_cmd_buffer_0;
+        }
+    }
+
+    SDL_UnlockMutex(RPC_Mutex);
+
+
+#define RPC_Get(foo)                \
+    RPC_data_##foo##_t data;        \
+    len = sizeof (data);            \
+    SDL_memcpy(&data, cmd_buffer, len);   \
+
+
+#define RPC_GetNoData               \
+    len = sizeof (RPC_data_t);      \
+
+
+    while (nb_cmd--) {
+        RPC_cmd_t cmd;
+        int len; // don't initialize so that it can trigger some warning with `-Wsometimes-uninitialized` if RPC_Get*() has be forgotten
+        SDL_memcpy(&cmd, cmd_buffer, sizeof (cmd));
+
+#ifdef DEBUG_RPC
+        SDL_Log("RPC cmd: %s", cmd2Str(cmd));
+#endif
+
+        switch (cmd) {
+
+                // ------------------------------
+                // SDLActivity_tab
+                // ------------------------------
+            case RPC_cmd_onNativeDropFile:
+                {
+                    RPC_Get(onNativeDropFile);
+                    SDL_SendDropFile(NULL, NULL, data.filename);
+                    SDL_SendDropComplete(NULL);
+                    SDL_free(data.filename);
+                }
+                break;
+
+            case RPC_cmd_nativeSetScreenResolution:
+                {
+                    RPC_Get(nativeSetScreenResolution);
+                    Android_SetScreenResolution(data.surfaceWidth, data.surfaceHeight, data.deviceWidth, data.deviceHeight, data.density, data.rate);
+                }
+                break;
+
+            case RPC_cmd_onNativeResize:
+                {
+                    RPC_GetNoData;
+                    Android_SendResize(window);
+                }
+                break;
+
+            case RPC_cmd_onNativeSurfaceCreated:
+                {
+                    RPC_GetNoData;
+                    Android_nativeSurfaceCreated(window);
+                }
+                break;
+
+            case RPC_cmd_onNativeSurfaceChanged:
+                {
+                    RPC_GetNoData;
+                    Android_nativeSurfaceChanged(window);
+
+                    if (window) {
+                        Android_RestoreScreenKeyboard(SDL_GetVideoDevice(), window);
+                    }
+                }
+                break;
+
+            case RPC_cmd_onNativeSurfaceDestroyed:
+                {
+                    RPC_GetNoData;
+
+                    // Pumped all events, so that we enter the Pause state.
+                    // and the GL context is backed up, before we destroy EGL Surface and native_window
+                    //
+                    // That's how it was before. Doesn't seem to make a big difference...
+
+                    Android_PumpLifecycleEvents(window);
+
+                    Android_nativeSurfaceDestroyed(window);
+                }
+                break;
+
+            case RPC_cmd_onNativeScreenKeyboardShown:
+                {
+                    RPC_GetNoData;
+                    SDL_SendScreenKeyboardShown();
+                }
+                break;
+
+            case RPC_cmd_onNativeScreenKeyboardHidden:
+                {
+                    RPC_GetNoData;
+                    SDL_SendScreenKeyboardHidden();
+                }
+                break;
+
+            case RPC_cmd_onNativeKeyDown:
+                {
+                    RPC_Get(onNativeKeyDown);
+                    Android_OnKeyDown(data.keycode);
+                }
+                break;
+
+            case RPC_cmd_onNativeKeyUp:
+                {
+                    RPC_Get(onNativeKeyUp);
+                    Android_OnKeyUp(data.keycode);
+                }
+                break;
+
+            case RPC_cmd_onNativeSoftReturnKey:
+                {
+                    RPC_GetNoData;
+                    SDL_StopTextInput(window);
+                }
+                break;
+
+            case RPC_cmd_onNativeKeyboardFocusLost:
+                {
+                    RPC_GetNoData;
+
+                    // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget
+                    SDL_StopTextInput(window);
+                }
+                break;
+
+            case RPC_cmd_onNativeTouch:
+                {
+                    RPC_Get(onNativeTouch);
+                    Android_OnTouch(window, data.touch_device_id_in, data.pointer_finger_id_in, data.action, data.x, data.y, data.p);
+                }
+                break;
+
+            case RPC_cmd_onNativePinchStart:
+                {
+                    RPC_GetNoData;
+                    SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0);
+                }
+                break;
+
+            case RPC_cmd_onNativePinchUpdate:
+                {
+                    RPC_Get(onNativePinchUpdate);
+                    SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, data.scale);
+                }
+                break;
+
+            case RPC_cmd_onNativePinchEnd:
+                {
+                    RPC_GetNoData;
+                    SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0);
+                }
+                break;
+
+            case RPC_cmd_onNativeMouse:
+                {
+                    RPC_Get(onNativeMouse);
+                    Android_OnMouse(window, data.button, data.action, data.x, data.y, data.relative);
+                }
+                break;
+
+            case RPC_cmd_onNativePen:
+                {
+                    RPC_Get(onNativePen);
+                    Android_OnPen(window, data.pen_id_in, data.device_type, data.button, data.action, data.x, data.y, data.p);
+                }
+                break;
+
+            case RPC_cmd_onNativeAccel:
+                {
+                    RPC_Get(onNativeAccel);
+                    fLastAccelerometer[0] = data.x;
+                    fLastAccelerometer[1] = data.y;
+                    fLastAccelerometer[2] = data.z;
+                    bHasNewData = true;
+                }
+                break;
+
+            case RPC_cmd_onNativeClipboardChanged:
+                {
+                    RPC_GetNoData;
+
+                    // TODO: compute new mime types
+                    SDL_SendClipboardUpdate(false, NULL, 0);
+                }
+                break;
+
+            case RPC_cmd_onNativeLocaleChanged:
+                {
+                    RPC_GetNoData;
+                    SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED);
+                }
+                break;
+
+            case RPC_cmd_onNativeDarkModeChanged:
+                {
+                    RPC_Get(onNativeDarkModeChanged);
+                    Android_SetDarkMode(data.enabled);
+                }
+                break;
+
+            case RPC_cmd_nativeFocusChanged:
+                {
+                    RPC_Get(nativeFocusChanged);
+                    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()");
+                    SDL_SendWindowEvent(window, (data.hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0);
+                }
+                break;
+
+            case RPC_cmd_nativeSetNaturalOrientation:
+                {
+                    RPC_Get(nativeSetNaturalOrientation);
+                    displayNaturalOrientation = (SDL_DisplayOrientation)data.orientation;
+                }
+                break;
+
+            case RPC_cmd_onNativeRotationChanged:
+                {
+                    RPC_Get(onNativeRotationChanged);
+
+                    if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) {
+                        data.rotation += 90;
+                    }
+
+                    switch (data.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;
+                    }
+
+                    Android_SetOrientation(displayCurrentOrientation);
+
+                }
+                break;
+
+            case RPC_cmd_onNativeInsetsChanged:
+                {
+                    RPC_Get(onNativeInsetsChanged);
+                    Android_SetWindowSafeAreaInsets(window, data.left, data.right, data.top, data.bottom);
+                }
+                break;
+
+            case RPC_cmd_nativeAddTouch:
+                {
+                    RPC_Get(nativeAddTouch);
+                    SDL_AddTouch(Android_ConvertJavaTouchID(data.touchId), SDL_TOUCH_DEVICE_DIRECT, data.name);
+                    SDL_free(data.name);
+                }
+                break;
+
+            case RPC_cmd_nativePermissionResult:
+                {
+                    RPC_Get(nativePermissionResult);
+
+                    NativePermissionRequestInfo *prev = &pending_permissions;
+                    for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) {
+                        if (info->request_code == (int) data.requestCode) {
+                            prev->next = info->next;
+                            info->callback(info->userdata, info->permission, data.result ? true : false);
+                            SDL_free(info->permission);
+                            SDL_free(info);
+
+                            /* done */
+                            break;
+                        }
+                        prev = info;
+                    }
+
+                }
+                break;
+
+            case RPC_cmd_onNativeFileDialog:
+                {
+                    RPC_Get(onNativeFileDialog);
+
+                    if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == data.requestCode) {
+
+                        // Handle some error ...
+                        if (!data.charFileList) {
+                            mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
+                            mAndroidFileDialogData.callback = NULL;
+                            goto onNativeFileDialog_end;
+                        } else {
+                            for (int i = 0; i < data.count; i++) {
+                                if (!data.charFileList[i]) {
+                                    mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
+                                    mAndroidFileDialogData.callback = NULL;
+                                    goto onNativeFileDialog_end;
+                                }
+                            }
+                        }
+
+                        // Call user-provided callback
+                        SDL_ClearError();
+                        mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) data.charFileList, data.filter);
+                        mAndroidFileDialogData.callback = NULL;
+                    }
+
+onNativeFileDialog_end:
+
+                    // Cleanup memory
+                    for (int i = 0; i < data.count; i++) {
+                        SDL_free(data.charFileList[i]);
+                    }
+                    SDL_free(data.charFileList);
+                }
+                break;
+
+                // ------------------------------
+                // SDLInputConnection_tab
+                // ------------------------------
+            case RPC_cmd_nativeCommitText:
+                {
+                    RPC_Get(nativeCommitText);
+                    SDL_SendKeyboardText(data.text);
+                    SDL_free(data.text);
+                }
+                break;
+
+            case RPC_cmd_nativeGenerateScancodeForUnichar:
+                {
+                    RPC_Get(nativeGenerateScancodeForUnichar);
+                    SDL_SendKeyboardUnicodeKey(0, data.chUnicode);
+                }
+                break;
+
+
+                // ------------------------------
+                // SDLAudioManager_tab
+                // ------------------------------
+            case RPC_cmd_nativeAddAudioDevice:
+                {
+                    RPC_Get(nativeAddAudioDevice);
+#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
+                    if (SDL_GetCurrentAudioDriver() != NULL) {
+                        void *handle = (void *)((size_t)data.device_id);
+                        if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) {
+                            SDL_AddAudioDevice(data.recording, SDL_strdup(data.name), NULL, handle);
+                        }
+                    }
+#endif
+                    SDL_free(data.name);
+                }
+
+                break;
+
+            case RPC_cmd_nativeRemoveAudioDevice:
+                {
+                    RPC_Get(nativeRemoveAudioDevice);
+#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
+                    if (SDL_GetCurrentAudioDriver() != NULL) {
+                        SDL_Log("Removing device with handle %d, recording %d", data.device_id, data.recording);
+                        SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)data.device_id)));
+                    }
+#endif
+                }
+                break;
+
+                // ------------------------------
+                // SDLControllerManager_tab
+                // ------------------------------
+            case RPC_cmd_onNativePadDown:
+                {
+                    RPC_Get(onNativePadDown);
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_OnPadDown(data.device_id, data.keycode);
+#endif
+                }
+                break;
+            case RPC_cmd_onNativePadUp:
+                {
+                    RPC_Get(onNativePadUp);
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_OnPadUp(data.device_id, data.keycode);
+#endif
+                }
+                break;
+
+            case RPC_cmd_onNativeJoy:
+                {
+                    RPC_Get(onNativeJoy);
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_OnJoy(data.device_id, data.axis, data.value);
+#endif
+                }
+                break;
+
+            case RPC_cmd_onNativeHat:
+                {
+                    RPC_Get(onNativeHat);
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_OnHat(data.device_id, data.hat_id, data.x, data.y);
+#endif
+                }
+                break;
+
+            case RPC_cmd_nativeAddJoystick:
+                {
+                    RPC_Get(nativeAddJoystick);
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_AddJoystick(data.device_id, data.device_name, data.device_desc,
+                            data.vendor_id, data.product_id, data.button_mask, data.naxes,
+                            data.axis_mask, data.nhats, data.can_rumble, data.has_rgb_led);
+#endif
+                    SDL_free(data.device_name);
+                    SDL_free(data.device_desc);
+
+                }
+                break;
+
+            case RPC_cmd_nativeRemoveJoystick:
+                {
+                    RPC_Get(nativeRemoveJoystick);
+
+#ifdef SDL_JOYSTICK_ANDROID
+                    Android_RemoveJoystick(data.device_id);
+#endif
+
+                }
+                break;
+
+            case RPC_cmd_nativeAddHaptic:
+                {
+                    RPC_Get(nativeAddHaptic);
+
+#ifdef SDL_HAPTIC_ANDROID
+                    Android_AddHaptic(data.device_id, data.device_name);
+
+#endif
+                    SDL_free(data.device_name);
+
+                }
+                break;
+
+            case RPC_cmd_nativeRemoveHaptic:
+                {
+                    RPC_Get(nativeRemoveHaptic);
+#ifdef SDL_HAPTIC_ANDROID
+                    Android_RemoveHaptic(data.device_id);
+#endif
+
+                }
+                break;
+
+                // ------------------------------
+                // HIDDeviceManager_tab
+                // ------------------------------
+
+            default:
+                len = 0;
+                SDL_Log("Error unknown RPC cmd %u", cmd);
+
+        }
+
+        cmd_buffer += len;
+    }
+
+}
+
 #endif // SDL_PLATFORM_ANDROID
diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h
index 9bb44eb..4d71582 100644
--- a/src/core/android/SDL_android.h
+++ b/src/core/android/SDL_android.h
@@ -52,8 +52,8 @@
 void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event);
 bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS);
 
-void Android_LockActivityMutex(void);
-void Android_UnlockActivityMutex(void);
+void Android_LockActivityState(void);
+void Android_UnlockActivityState(void);
 
 void Android_SetAllowRecreateActivity(bool enabled);
 
@@ -157,6 +157,10 @@
     const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
     bool multiple);
 
+// Pump RPC commands
+void Android_PumpRPC(SDL_Window *window);
+
+
 // Ends C function definitions when using C++
 #ifdef __cplusplus
 /* *INDENT-OFF* */
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 4d6fa0e..8bebf75 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -1513,7 +1513,9 @@
 
 #ifdef SDL_PLATFORM_ANDROID
     // Android event processing is independent of the video subsystem
-    Android_PumpEvents(0);
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    SDL_Window *window = _this->windows;
+    Android_PumpEvents(window, 0);
 #else
     // Get events from the video subsystem
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
@@ -1746,7 +1748,10 @@
             }
             delay = (expiration - now);
         }
-        Android_PumpEvents(delay);
+
+        SDL_VideoDevice *_this = SDL_GetVideoDevice();
+        SDL_Window *window = _this->windows;
+        Android_PumpEvents(window, delay);
     }
 #else
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c
index 34fdb61..04119a4 100644
--- a/src/joystick/android/SDL_sysjoystick.c
+++ b/src/joystick/android/SDL_sysjoystick.c
@@ -64,7 +64,7 @@
  * This code manipulation is done to get a sequential list of codes.
  * FIXME: This is only suited for the case where we use a fixed number of buttons determined by ANDROID_MAX_NBUTTONS
  */
-static int keycode_to_SDL(int keycode)
+int android_keycode_to_SDL(int keycode)
 {
     // FIXME: If this function gets too unwieldy in the future, replace with a lookup table
     int button = 0;
@@ -198,7 +198,7 @@
 {
     Uint64 timestamp = SDL_GetTicksNS();
     SDL_joylist_item *item;
-    int button = keycode_to_SDL(keycode);
+    int button = android_keycode_to_SDL(keycode);
     if (button >= 0) {
         SDL_LockJoysticks();
         item = JoystickByDeviceId(device_id);
@@ -218,7 +218,7 @@
 {
     Uint64 timestamp = SDL_GetTicksNS();
     SDL_joylist_item *item;
-    int button = keycode_to_SDL(keycode);
+    int button = android_keycode_to_SDL(keycode);
     if (button >= 0) {
         SDL_LockJoysticks();
         item = JoystickByDeviceId(device_id);
diff --git a/src/joystick/android/SDL_sysjoystick_c.h b/src/joystick/android/SDL_sysjoystick_c.h
index 48f7ae2..32aa9a6 100644
--- a/src/joystick/android/SDL_sysjoystick_c.h
+++ b/src/joystick/android/SDL_sysjoystick_c.h
@@ -28,6 +28,7 @@
 
 #include "../SDL_sysjoystick.h"
 
+extern int android_keycode_to_SDL(int keycode);
 extern bool Android_OnPadDown(int device_id, int keycode);
 extern bool Android_OnPadUp(int device_id, int keycode);
 extern bool Android_OnJoy(int device_id, int axisnum, float value);
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 3d1c95f..a63ee55 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -1069,7 +1069,7 @@
     }
 
 #ifdef SDL_PLATFORM_ANDROID
-    if (!Android_WaitActiveAndLockActivity()) {
+    if (!Android_WaitActiveAndLockActivity(window)) {
         return NULL;
     }
 #endif
@@ -1249,7 +1249,7 @@
     SDL_renderers = renderer;
 
 #ifdef SDL_PLATFORM_ANDROID
-    Android_UnlockActivityMutex();
+    Android_UnlockActivityState();
 #endif
 
     SDL_ClearError();
@@ -1258,7 +1258,7 @@
 
 error:
 #ifdef SDL_PLATFORM_ANDROID
-    Android_UnlockActivityMutex();
+    Android_UnlockActivityState();
 #endif
 
     if (renderer) {
diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c
index 4bdd717..ea8c054 100644
--- a/src/video/android/SDL_androidevents.c
+++ b/src/video/android/SDL_androidevents.c
@@ -35,43 +35,40 @@
 #ifdef SDL_VIDEO_OPENGL_EGL
 static void android_egl_context_restore(SDL_Window *window)
 {
-    if (window) {
-        SDL_WindowData *data = window->internal;
-        SDL_GL_MakeCurrent(window, NULL);
-        if (!SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context)) {
-            // The context is no longer valid, create a new one
-            data->egl_context = (EGLContext)SDL_GL_CreateContext(window);
-            SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context);
-            SDL_Event event;
-            SDL_zero(event);
-            event.type = SDL_EVENT_RENDER_DEVICE_RESET;
-            event.render.windowID = SDL_GetWindowID(window);
-            SDL_PushEvent(&event);
-        }
-        data->backup_done = false;
-
-        SDL_GL_SetSwapInterval(data->swap_interval);
+    SDL_WindowData *data = window->internal;
+    SDL_GL_MakeCurrent(window, NULL);
+    if (!SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context)) {
+        // The context is no longer valid, create a new one
+        data->egl_context = (EGLContext)SDL_GL_CreateContext(window);
+        SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context);
+        SDL_Event event;
+        SDL_zero(event);
+        event.type = SDL_EVENT_RENDER_DEVICE_RESET;
+        event.render.windowID = SDL_GetWindowID(window);
+        SDL_PushEvent(&event);
     }
+    data->backup_done = false;
+
+    SDL_GL_SetSwapInterval(data->swap_interval);
 }
 
 static void android_egl_context_backup(SDL_Window *window)
 {
-    if (window) {
-        int interval = 0;
-        // Keep a copy of the EGL Context so we can try to restore it when we resume
-        SDL_WindowData *data = window->internal;
-        data->egl_context = SDL_GL_GetCurrentContext();
+    int interval = 0;
+    // Keep a copy of the EGL Context so we can try to restore it when we resume
+    SDL_WindowData *data = window->internal;
+    data->egl_context = SDL_GL_GetCurrentContext();
 
-        // Save/Restore the swap interval / vsync
-        if (SDL_GL_GetSwapInterval(&interval)) {
-            data->has_swap_interval = 1;
-            data->swap_interval = interval;
-        }
-
-        // We need to do this so the EGLSurface can be freed
-        SDL_GL_MakeCurrent(window, NULL);
-        data->backup_done = true;
+    SDL_Log("android_egl_context_backup ...");
+    // Save/Restore the swap interval / vsync
+    if (SDL_GL_GetSwapInterval(&interval)) {
+        data->has_swap_interval = 1;
+        data->swap_interval = interval;
     }
+
+    // We need to do this so the EGLSurface can be freed
+    SDL_GL_MakeCurrent(window, NULL);
+    data->backup_done = true;
 }
 #endif
 
@@ -110,7 +107,7 @@
     }
 }
 
-static void Android_OnPause(void)
+static void Android_OnPause(SDL_Window *window)
 {
     SDL_OnApplicationWillEnterBackground();
     SDL_OnApplicationDidEnterBackground();
@@ -121,10 +118,8 @@
      * was being queued.
      */
 #ifdef SDL_VIDEO_OPENGL_EGL
-    if (Android_Window && !Android_Window->external_graphics_context) {
-        Android_LockActivityMutex();
-        android_egl_context_backup(Android_Window);
-        Android_UnlockActivityMutex();
+    if (window && !window->external_graphics_context) {
+        android_egl_context_backup(window);
     }
 #endif
 
@@ -136,7 +131,7 @@
     Android_Paused = true;
 }
 
-static void Android_OnResume(void)
+static void Android_OnResume(SDL_Window *window)
 {
     Android_Paused = false;
 
@@ -146,10 +141,8 @@
 
 #ifdef SDL_VIDEO_OPENGL_EGL
     // Restore the GL Context from here, as this operation is thread dependent
-    if (Android_Window && !Android_Window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) {
-        Android_LockActivityMutex();
-        android_egl_context_restore(Android_Window);
-        Android_UnlockActivityMutex();
+    if (window && !window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) {
+        android_egl_context_restore(window);
     }
 #endif
 
@@ -176,17 +169,17 @@
     Android_Destroyed = true;
 }
 
-static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event)
+static void Android_HandleLifecycleEvent(SDL_Window *window, SDL_AndroidLifecycleEvent event)
 {
     switch (event) {
     case SDL_ANDROID_LIFECYCLE_WAKE:
         // Nothing to do, just return
         break;
     case SDL_ANDROID_LIFECYCLE_PAUSE:
-        Android_OnPause();
+        Android_OnPause(window);
         break;
     case SDL_ANDROID_LIFECYCLE_RESUME:
-        Android_OnResume();
+        Android_OnResume(window);
         break;
     case SDL_ANDROID_LIFECYCLE_LOWMEMORY:
         Android_OnLowMemory();
@@ -211,14 +204,17 @@
     return timeoutNS;
 }
 
-void Android_PumpEvents(Sint64 timeoutNS)
+void Android_PumpEvents(SDL_Window *window, Sint64 timeoutNS)
 {
     SDL_AndroidLifecycleEvent event;
     bool paused = Android_Paused;
 
     while (!Android_Destroyed &&
            Android_WaitLifecycleEvent(&event, GetLifecycleEventTimeout(paused, timeoutNS))) {
-        Android_HandleLifecycleEvent(event);
+
+        Android_PumpRPC(window);
+
+        Android_HandleLifecycleEvent(window, event);
 
         switch (event) {
         case SDL_ANDROID_LIFECYCLE_WAKE:
@@ -238,24 +234,47 @@
     }
 }
 
-bool Android_WaitActiveAndLockActivity(void)
+
+void Android_PumpLifecycleEvents(SDL_Window *window)
 {
-    /* Make sure we have pumped all events so that Android_Paused state is correct */
+    // Make sure we have pumped all events so that Android_Paused state is correct
     SDL_AndroidLifecycleEvent event;
     while (!Android_Destroyed && Android_WaitLifecycleEvent(&event, 0)) {
-        Android_HandleLifecycleEvent(event);
+        Android_HandleLifecycleEvent(window, event);
     }
+}
 
-    while (Android_Paused && !Android_Destroyed) {
-        Android_PumpEvents(-1);
-    }
+bool Android_WaitActiveAndLockActivity(SDL_Window *window)
+{
+retry:
+
+    // Lock first.
+    // So no new lifecycle event comes in the meantimes from the SDLActivity,
+    // Hence SDLActivity won't change state.
+    Android_LockActivityState();
+
+    // Make sure we have pumped all events so that Android_Paused state is correct
+    Android_PumpLifecycleEvents(window);
 
     if (Android_Destroyed) {
         SDL_SetError("Android activity has been destroyed");
+        Android_UnlockActivityState();
         return false;
     }
 
-    Android_LockActivityMutex();
+    if (!Android_Paused) {
+        // SDLActivity is Active. return lock'ed
+        Android_PumpRPC(window);
+        return true;
+    } else {
+        // Still Paused
+        // Unlock and wait for the SDLActivity to send new cycle events
+        // or for the user to move the app to foreground.
+        Android_UnlockActivityState();
+        SDL_Delay(10);
+        goto retry;
+    }
+
     return true;
 }
 
diff --git a/src/video/android/SDL_androidevents.h b/src/video/android/SDL_androidevents.h
index 7328f2c..b70a69f 100644
--- a/src/video/android/SDL_androidevents.h
+++ b/src/video/android/SDL_androidevents.h
@@ -21,6 +21,8 @@
 #include "SDL_internal.h"
 
 extern void Android_InitEvents(void);
-extern void Android_PumpEvents(Sint64 timeoutNS);
-extern bool Android_WaitActiveAndLockActivity(void);
+extern void Android_PumpEvents(SDL_Window *window, Sint64 timeoutNS);
+extern bool Android_WaitActiveAndLockActivity(SDL_Window *window);
 extern void Android_QuitEvents(void);
+
+extern void Android_PumpLifecycleEvents(SDL_Window *window);
diff --git a/src/video/android/SDL_androidgl.c b/src/video/android/SDL_androidgl.c
index fb98d03..1de0255 100644
--- a/src/video/android/SDL_androidgl.c
+++ b/src/video/android/SDL_androidgl.c
@@ -49,13 +49,13 @@
 {
     SDL_GLContext result;
 
-    if (!Android_WaitActiveAndLockActivity()) {
+    if (!Android_WaitActiveAndLockActivity(window)) {
         return NULL;
     }
 
     result = SDL_EGL_CreateContext(_this, window->internal->egl_surface);
 
-    Android_UnlockActivityMutex();
+    Android_UnlockActivityState();
 
     return result;
 }
@@ -64,8 +64,6 @@
 {
     bool result;
 
-    Android_LockActivityMutex();
-
     /* The following two calls existed in the original Java code
      * If you happen to have a device that's affected by their removal,
      * please report to our bug tracker. -- Gabriel
@@ -75,8 +73,6 @@
     _this->egl_data->eglWaitGL();*/
     result = SDL_EGL_SwapBuffers(_this, window->internal->egl_surface);
 
-    Android_UnlockActivityMutex();
-
     return result;
 }
 
diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c
index d868e69..c37d7fc 100644
--- a/src/video/android/SDL_androidvideo.c
+++ b/src/video/android/SDL_androidvideo.c
@@ -300,15 +300,15 @@
     }
 }
 
-void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom)
+void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom)
 {
     Android_SafeInsetLeft = left;
     Android_SafeInsetRight = right;
     Android_SafeInsetTop = top;
     Android_SafeInsetBottom = bottom;
 
-    if (Android_Window) {
-        SDL_SetWindowSafeAreaInsets(Android_Window, left, right, top, bottom);
+    if (window) {
+        SDL_SetWindowSafeAreaInsets(window, left, right, top, bottom);
     }
 }
 
diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h
index 03bfb01..c695265 100644
--- a/src/video/android/SDL_androidvideo.h
+++ b/src/video/android/SDL_androidvideo.h
@@ -30,7 +30,7 @@
 extern void Android_SetFormat(int format_wanted, int format_got);
 extern void Android_SetOrientation(SDL_DisplayOrientation orientation);
 extern void Android_SendResize(SDL_Window *window);
-extern void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom);
+extern void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom);
 extern void Android_SetDarkMode(bool enabled);
 
 // Private display data
diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c
index 4652b5f..059649d 100644
--- a/src/video/android/SDL_androidwindow.c
+++ b/src/video/android/SDL_androidwindow.c
@@ -34,18 +34,32 @@
 
 
 // Currently only one window
-SDL_Window *Android_Window = NULL;
+static bool window_created = false;
 
 bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
 {
     SDL_WindowData *data;
     bool result = true;
 
-    if (!Android_WaitActiveAndLockActivity()) {
+    // Android_WaitActiveAndLockActivity() process some RPC:
+    // "onSurfaceCreated" that needs "window->internal"
+    // and also "nativeSetScreenResolution"
+
+    data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
+    if (!data) {
         return false;
     }
 
-    if (Android_Window) {
+#ifdef SDL_VIDEO_OPENGL_EGL
+    data->egl_surface = EGL_NO_SURFACE;
+#endif
+    window->internal = data;
+
+    if (!Android_WaitActiveAndLockActivity(window)) {
+        return false;
+    }
+
+    if (window_created) {
         result = SDL_SetError("Android only supports one window");
         goto endfunction;
     }
@@ -63,44 +77,23 @@
     SDL_SetMouseFocus(window);
     SDL_SetKeyboardFocus(window);
 
-    data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
-    if (!data) {
+    if (!Android_nativeSurfaceCreated(window)) {
         result = false;
         goto endfunction;
     }
 
-    data->native_window = Android_JNI_GetNativeWindow();
-    if (!data->native_window) {
-        SDL_free(data);
-        result = SDL_SetError("Could not fetch native window");
+    if (!Android_nativeSurfaceChanged(window)) {
+        result = false;
         goto endfunction;
     }
-    SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
-
-    /* Do not create EGLSurface for Vulkan window since it will then make the window
-       incompatible with vkCreateAndroidSurfaceKHR */
-#ifdef SDL_VIDEO_OPENGL_EGL
-    if (window->flags & SDL_WINDOW_OPENGL) {
-        data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window);
-
-        if (data->egl_surface == EGL_NO_SURFACE) {
-            ANativeWindow_release(data->native_window);
-            SDL_free(data);
-            result = false;
-            goto endfunction;
-        }
-    }
-    SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
-#endif
 
     SDL_SetWindowSafeAreaInsets(window, Android_SafeInsetLeft, Android_SafeInsetRight, Android_SafeInsetTop, Android_SafeInsetBottom);
 
-    window->internal = data;
-    Android_Window = window;
+    window_created = true;
 
 endfunction:
 
-    Android_UnlockActivityMutex();
+    Android_UnlockActivityState();
 
     return result;
 }
@@ -112,54 +105,48 @@
 
 SDL_FullscreenResult Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
 {
-    Android_LockActivityMutex();
+    SDL_WindowData *data;
+    int old_w, old_h, new_w, new_h;
 
-    if (window == Android_Window) {
-        SDL_WindowData *data;
-        int old_w, old_h, new_w, new_h;
+    // If the window is being destroyed don't change visible state
+    if (!window->is_destroying) {
+        Android_JNI_SetWindowStyle(fullscreen);
+    }
 
-        // If the window is being destroyed don't change visible state
-        if (!window->is_destroying) {
-            Android_JNI_SetWindowStyle(fullscreen);
+    /* Ensure our size matches reality after we've executed the window style change.
+     *
+     * It is possible that we've set width and height to the full-size display, but on
+     * Samsung DeX or Chromebooks or other windowed Android environments, our window may
+     * still not be the full display size.
+     */
+    if (!SDL_IsDeXMode() && !SDL_IsChromebook()) {
+        goto endfunction;
+    }
+
+    data = window->internal;
+    if (!data || !data->native_window) {
+        if (data && !data->native_window) {
+            SDL_SetError("Missing native window");
         }
+        goto endfunction;
+    }
 
-        /* Ensure our size matches reality after we've executed the window style change.
-         *
-         * It is possible that we've set width and height to the full-size display, but on
-         * Samsung DeX or Chromebooks or other windowed Android environments, our window may
-         * still not be the full display size.
-         */
-        if (!SDL_IsDeXMode() && !SDL_IsChromebook()) {
-            goto endfunction;
-        }
+    old_w = window->w;
+    old_h = window->h;
 
-        data = window->internal;
-        if (!data || !data->native_window) {
-            if (data && !data->native_window) {
-                SDL_SetError("Missing native window");
-            }
-            goto endfunction;
-        }
+    new_w = ANativeWindow_getWidth(data->native_window);
+    new_h = ANativeWindow_getHeight(data->native_window);
 
-        old_w = window->w;
-        old_h = window->h;
+    if (new_w < 0 || new_h < 0) {
+        SDL_SetError("ANativeWindow_getWidth/Height() fails");
+    }
 
-        new_w = ANativeWindow_getWidth(data->native_window);
-        new_h = ANativeWindow_getHeight(data->native_window);
-
-        if (new_w < 0 || new_h < 0) {
-            SDL_SetError("ANativeWindow_getWidth/Height() fails");
-        }
-
-        if (old_w != new_w || old_h != new_h) {
-            SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h);
-        }
+    if (old_w != new_w || old_h != new_h) {
+        SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h);
     }
 
 endfunction:
 
-    Android_UnlockActivityMutex();
-
     return SDL_FULLSCREEN_SUCCEEDED;
 }
 
@@ -176,29 +163,86 @@
 
 void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
 {
-    Android_LockActivityMutex();
+    window_created = false;
 
-    if (window == Android_Window) {
-        Android_Window = NULL;
 
-        if (window->internal) {
-            SDL_WindowData *data = window->internal;
+    if (window->internal) {
+        Android_nativeSurfaceCreated(window);
+        SDL_free(window->internal);
+        window->internal = NULL;
+    }
+}
 
-#ifdef SDL_VIDEO_OPENGL_EGL
-            if (data->egl_surface != EGL_NO_SURFACE) {
-                SDL_EGL_DestroySurface(_this, data->egl_surface);
-            }
-#endif
 
-            if (data->native_window) {
-                ANativeWindow_release(data->native_window);
-            }
-            SDL_free(window->internal);
-            window->internal = NULL;
+// Those functions called from RPC onNativeSurface{Created,Changed,Destroyed}()
+// and SDL_{Create,Destroy}Window();
+
+bool Android_nativeSurfaceCreated(SDL_Window *window)
+{
+    if (window) {
+        SDL_WindowData *data = window->internal;
+
+        data->native_window = Android_JNI_GetNativeWindow();
+        SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
+        if (data->native_window == NULL) {
+            SDL_SetError("Could not fetch native window");
+            return false;
         }
+        return true;
     }
 
-    Android_UnlockActivityMutex();
+    return false;
+}
+
+bool Android_nativeSurfaceChanged(SDL_Window *window)
+{
+    if (window) {
+#ifdef SDL_VIDEO_OPENGL_EGL
+        /* Do not create EGLSurface for Vulkan window since it will then make the window
+           incompatible with vkCreateAndroidSurfaceKHR */
+        if (window->flags & SDL_WINDOW_OPENGL) {
+            SDL_VideoDevice *_this = SDL_GetVideoDevice();
+            SDL_WindowData *data = window->internal;
+
+            // If the surface has been previously destroyed by onNativeSurfaceDestroyed
+            // or if it is the first time, recreate it.
+            if (data->egl_surface == EGL_NO_SURFACE) {
+                data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window);
+                SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
+            }
+
+            if (data->egl_surface == EGL_NO_SURFACE) {
+                if (data->native_window) {
+                    ANativeWindow_release(data->native_window);
+                    data->native_window = 0;
+                }
+                return false;
+            }
+        }
+#endif
+        return true;
+    }
+
+    return false;
+}
+
+void Android_nativeSurfaceDestroyed(SDL_Window *window)
+{
+    if (window) {
+        SDL_WindowData *data = window->internal;
+
+#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;
+        }
+    }
 }
 
 #endif // SDL_VIDEO_DRIVER_ANDROID
diff --git a/src/video/android/SDL_androidwindow.h b/src/video/android/SDL_androidwindow.h
index cc24da9..b788f3b 100644
--- a/src/video/android/SDL_androidwindow.h
+++ b/src/video/android/SDL_androidwindow.h
@@ -33,7 +33,6 @@
 extern void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable);
 
 extern void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
-extern SDL_Window *Android_Window;
 
 struct SDL_WindowData
 {
@@ -48,4 +47,11 @@
 
 };
 
+
+bool Android_nativeSurfaceCreated(SDL_Window *window);
+bool Android_nativeSurfaceChanged(SDL_Window *window);
+void Android_nativeSurfaceDestroyed(SDL_Window *window);
+
+
+
 #endif // SDL_androidwindow_h_