Browse Source

Handle all Android lifecycle events on the main thread

This restructuring also allows us to wait efficiently in SDL_WaitEvent() on Android
Sam Lantinga 8 months ago
parent
commit
c601120883

+ 121 - 62
src/core/android/SDL_android.c

@@ -404,8 +404,10 @@ static jobject javaAssetManagerRef = 0;
 static SDL_AtomicInt bAllowRecreateActivity;
 
 static SDL_Mutex *Android_ActivityMutex = NULL;
-SDL_Semaphore *Android_PauseSem = NULL;
-SDL_Semaphore *Android_ResumeSem = 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;
 
 /*******************************************************************************
                  Functions called by JNI
@@ -614,14 +616,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex");
     }
 
-    Android_PauseSem = SDL_CreateSemaphore(0);
-    if (!Android_PauseSem) {
-        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_PauseSem semaphore");
+    Android_LifecycleMutex = SDL_CreateMutex();
+    if (!Android_LifecycleMutex) {
+        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleMutex mutex");
     }
 
-    Android_ResumeSem = SDL_CreateSemaphore(0);
-    if (!Android_ResumeSem) {
-        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ResumeSem semaphore");
+    Android_LifecycleEventSem = SDL_CreateSemaphore(0);
+    if (!Android_LifecycleEventSem) {
+        __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleEventSem semaphore");
     }
 
     mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls));
@@ -895,46 +897,120 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls,
     return status;
 }
 
-/* Drop file */
-JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
-    JNIEnv *env, jclass jcls,
-    jstring filename)
+static int FindLifecycleEvent(SDL_AndroidLifecycleEvent event)
 {
-    const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
-    SDL_SendDropFile(NULL, NULL, path);
-    (*env)->ReleaseStringUTFChars(env, filename, path);
-    SDL_SendDropComplete(NULL);
+    for (int index = 0; index < Android_NumLifecycleEvents; ++index) {
+        if (Android_LifecycleEvents[index] == event) {
+            return index;
+        }
+    }
+    return -1;
 }
 
-/* Lock / Unlock Mutex */
-void Android_LockActivityMutex(void)
+static void RemoveLifecycleEvent(int index)
 {
-    SDL_LockMutex(Android_ActivityMutex);
+    if (index < Android_NumLifecycleEvents - 1) {
+        SDL_memcpy(&Android_LifecycleEvents[index], &Android_LifecycleEvents[index+1], (Android_NumLifecycleEvents - index - 1) * sizeof(Android_LifecycleEvents[index]));
+    }
+    --Android_NumLifecycleEvents;
 }
 
-void Android_UnlockActivityMutex(void)
+void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event)
 {
-    SDL_UnlockMutex(Android_ActivityMutex);
+    SDL_LockMutex(Android_LifecycleMutex);
+    {
+        int index;
+        SDL_bool add_event = SDL_TRUE;
+
+        switch (event) {
+        case SDL_ANDROID_LIFECYCLE_WAKE:
+            // We don't need more than one wake queued
+            index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE);
+            if (index >= 0) {
+                add_event = SDL_FALSE;
+            }
+            break;
+        case SDL_ANDROID_LIFECYCLE_PAUSE:
+            // If we have a resume queued, just stay in the paused state
+            index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME);
+            if (index >= 0) {
+                RemoveLifecycleEvent(index);
+                add_event = SDL_FALSE;
+            }
+            break;
+        case SDL_ANDROID_LIFECYCLE_RESUME:
+            // If we have a pause queued, just stay in the resumed state
+            index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE);
+            if (index >= 0) {
+                RemoveLifecycleEvent(index);
+                add_event = SDL_FALSE;
+            }
+            break;
+        case SDL_ANDROID_LIFECYCLE_LOWMEMORY:
+            // We don't need more than one low memory event queued
+            index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY);
+            if (index >= 0) {
+                add_event = SDL_FALSE;
+            }
+            break;
+        case SDL_ANDROID_LIFECYCLE_DESTROY:
+            // Remove all other events, we're done!
+            while (Android_NumLifecycleEvents > 0) {
+                RemoveLifecycleEvent(0);
+            }
+            break;
+        default:
+            SDL_assert(!"Sending unexpected lifecycle event");
+            add_event = SDL_FALSE;
+            break;
+        }
+
+        if (add_event) {
+            SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents));
+            Android_LifecycleEvents[Android_NumLifecycleEvents++] = event;
+            SDL_SignalSemaphore(Android_LifecycleEventSem);
+        }
+    }
+    SDL_UnlockMutex(Android_LifecycleMutex);
 }
 
-/* Lock the Mutex when the Activity is in its 'Running' state */
-void Android_LockActivityMutexOnceRunning(void)
+SDL_bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS)
 {
-    int pauseSignaled = 0;
-    int resumeSignaled = 0;
+    SDL_bool got_event = SDL_FALSE;
 
-retry:
+    while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS) == 0) {
+        SDL_LockMutex(Android_LifecycleMutex);
+        {
+            if (Android_NumLifecycleEvents > 0) {
+                *event = Android_LifecycleEvents[0];
+                RemoveLifecycleEvent(0);
+                got_event = SDL_TRUE;
+            }
+        }
+        SDL_UnlockMutex(Android_LifecycleMutex);
+    }
+    return got_event;
+}
 
+void Android_LockActivityMutex(void)
+{
     SDL_LockMutex(Android_ActivityMutex);
+}
 
-    pauseSignaled = SDL_GetSemaphoreValue(Android_PauseSem);
-    resumeSignaled = SDL_GetSemaphoreValue(Android_ResumeSem);
+void Android_UnlockActivityMutex(void)
+{
+    SDL_UnlockMutex(Android_ActivityMutex);
+}
 
-    if (pauseSignaled > resumeSignaled) {
-        SDL_UnlockMutex(Android_ActivityMutex);
-        SDL_Delay(50);
-        goto retry;
-    }
+/* Drop file */
+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);
 }
 
 /* Set screen resolution */
@@ -1335,7 +1411,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
     JNIEnv *env, jclass cls)
 {
-    SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY);
+    Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY);
 }
 
 /* Locale
@@ -1357,20 +1433,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
     JNIEnv *env, jclass cls)
 {
-    /* Discard previous events. The user should have handled state storage
-     * in SDL_EVENT_WILL_ENTER_BACKGROUND. After nativeSendQuit() is called, no
-     * events other than SDL_EVENT_QUIT and SDL_EVENT_TERMINATING should fire */
-    SDL_FlushEvents(SDL_EVENT_FIRST, SDL_EVENT_LAST);
-    /* Inject a SDL_EVENT_QUIT event */
-    SDL_SendQuit();
-    SDL_SendAppEvent(SDL_EVENT_TERMINATING);
-    /* Robustness: clear any pending Pause */
-    while (SDL_TryWaitSemaphore(Android_PauseSem) == 0) {
-        /* empty */
-    }
-    /* Resume the event loop so that the app can catch SDL_EVENT_QUIT which
-     * should now be the top event in the event queue. */
-    SDL_SignalSemaphore(Android_ResumeSem);
+    Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_DESTROY);
 }
 
 /* Activity ends */
@@ -1384,16 +1447,18 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
         Android_ActivityMutex = NULL;
     }
 
-    if (Android_PauseSem) {
-        SDL_DestroySemaphore(Android_PauseSem);
-        Android_PauseSem = NULL;
+    if (Android_LifecycleMutex) {
+        SDL_DestroyMutex(Android_LifecycleMutex);
+        Android_LifecycleMutex = NULL;
     }
 
-    if (Android_ResumeSem) {
-        SDL_DestroySemaphore(Android_ResumeSem);
-        Android_ResumeSem = NULL;
+    if (Android_LifecycleEventSem) {
+        SDL_DestroySemaphore(Android_LifecycleEventSem);
+        Android_LifecycleEventSem = NULL;
     }
 
+    Android_NumLifecycleEvents = 0;
+
     Internal_Android_Destroy_AssetManager();
 
     str = SDL_GetError();
@@ -1410,9 +1475,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
 {
     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
 
-    /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself.
-     * Sometimes 2 pauses can be queued (eg pause/resume/pause), so it's always increased. */
-    SDL_SignalSemaphore(Android_PauseSem);
+    Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE);
 }
 
 /* Resume */
@@ -1421,11 +1484,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
 {
     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
 
-    /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
-     * We can't restore the GL Context here because it needs to be done on the SDL main thread
-     * and this function will be called from the Java thread instead.
-     */
-    SDL_SignalSemaphore(Android_ResumeSem);
+    Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME);
 }
 
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(

+ 22 - 11
src/core/android/SDL_android.h

@@ -20,6 +20,9 @@
 */
 #include "SDL_internal.h"
 
+#ifndef SDL_android_h
+#define SDL_android_h
+
 /* Set up for C function definitions, even when using C++ */
 #ifdef __cplusplus
 /* *INDENT-OFF* */
@@ -35,6 +38,23 @@ extern "C" {
 // this appears to be broken right now (on Android, not SDL, I think...?).
 #define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0
 
+/* Life cycle */
+typedef enum
+{
+    SDL_ANDROID_LIFECYCLE_WAKE,
+    SDL_ANDROID_LIFECYCLE_PAUSE,
+    SDL_ANDROID_LIFECYCLE_RESUME,
+    SDL_ANDROID_LIFECYCLE_LOWMEMORY,
+    SDL_ANDROID_LIFECYCLE_DESTROY,
+    SDL_NUM_ANDROID_LIFECYCLE_EVENTS
+} SDL_AndroidLifecycleEvent;
+
+void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event);
+SDL_bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS);
+
+void Android_LockActivityMutex(void);
+void Android_UnlockActivityMutex(void);
+
 /* Interface from the SDL library into the Android Java activity */
 extern void Android_JNI_SetActivityTitle(const char *title);
 extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen);
@@ -111,9 +131,6 @@ int Android_JNI_GetLocale(char *buf, size_t buflen);
 /* Generic messages */
 int Android_JNI_SendMessage(int command, int param);
 
-/* Init */
-JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv *mEnv, jclass cls);
-
 /* MessageBox */
 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
 
@@ -139,22 +156,16 @@ SDL_bool SDL_IsAndroidTV(void);
 SDL_bool SDL_IsChromebook(void);
 SDL_bool SDL_IsDeXMode(void);
 
-void Android_LockActivityMutex(void);
-void Android_UnlockActivityMutex(void);
-void Android_LockActivityMutexOnceRunning(void);
-
 /* File Dialogs */
 SDL_bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata,
     const SDL_DialogFileFilter *filters, int nfilters, SDL_bool forwrite,
     SDL_bool multiple);
 
-/* Semaphores for event state processing */
-extern SDL_Semaphore *Android_PauseSem;
-extern SDL_Semaphore *Android_ResumeSem;
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 /* *INDENT-OFF* */
 }
 /* *INDENT-ON* */
 #endif
+
+#endif // SDL_android_h

+ 34 - 4
src/events/SDL_events.c

@@ -34,7 +34,11 @@
 #include "../sensor/SDL_sensor_c.h"
 #endif
 #include "../video/SDL_sysvideo.h"
+
+#ifdef SDL_PLATFORM_ANDROID
+#include "../core/android/SDL_android.h"
 #include "../video/android/SDL_androidevents.h"
+#endif
 
 /* An arbitrary limit so we don't have unbounded growth */
 #define SDL_MAX_QUEUED_EVENTS 65535
@@ -1029,6 +1033,9 @@ static void SDL_CutEvent(SDL_EventEntry *entry)
 
 static int SDL_SendWakeupEvent(void)
 {
+#ifdef SDL_PLATFORM_ANDROID
+    Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE);
+#else
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
     if (_this == NULL || !_this->SendWakeupEvent) {
         return 0;
@@ -1044,6 +1051,7 @@ static int SDL_SendWakeupEvent(void)
         }
     }
     SDL_UnlockMutex(_this->wakeup_lock);
+#endif
 
     return 0;
 }
@@ -1182,7 +1190,7 @@ static void SDL_PumpEventsInternal(SDL_bool push_sentinel)
 
 #ifdef SDL_PLATFORM_ANDROID
     /* Android event processing is independent of the video subsystem */
-    Android_PumpEvents();
+    Android_PumpEvents(0);
 #else
     /* Get events from the video subsystem */
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
@@ -1241,6 +1249,8 @@ SDL_bool SDL_PollEvent(SDL_Event *event)
     return SDL_WaitEventTimeoutNS(event, 0);
 }
 
+#ifndef SDL_PLATFORM_ANDROID
+
 static Sint64 SDL_events_get_polling_interval(void)
 {
     Sint64 poll_intervalNS = SDL_MAX_SINT64;
@@ -1347,6 +1357,8 @@ static SDL_Window *SDL_find_active_window(SDL_VideoDevice *_this)
     return NULL;
 }
 
+#endif // !SDL_PLATFORM_ANDROID
+
 SDL_bool SDL_WaitEvent(SDL_Event *event)
 {
     return SDL_WaitEventTimeoutNS(event, -1);
@@ -1366,8 +1378,6 @@ SDL_bool SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS)
 
 SDL_bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS)
 {
-    SDL_VideoDevice *_this = SDL_GetVideoDevice();
-    SDL_Window *wakeup_window;
     Uint64 start, expiration;
     SDL_bool include_sentinel = (timeoutNS == 0);
     int result;
@@ -1420,9 +1430,28 @@ SDL_bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS)
     /* We should have completely handled timeoutNS == 0 above */
     SDL_assert(timeoutNS != 0);
 
+#ifdef SDL_PLATFORM_ANDROID
+    for (;;) {
+        if (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) > 0) {
+            return SDL_TRUE;
+        }
+
+        Uint64 delay = -1;
+        if (timeoutNS > 0) {
+            Uint64 now = SDL_GetTicksNS();
+            if (now >= expiration) {
+                /* Timeout expired and no events */
+                return SDL_FALSE;
+            }
+            delay = (expiration - now);
+        }
+        Android_PumpEvents(delay);
+    }
+#else
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
     if (_this && _this->WaitEventTimeout && _this->SendWakeupEvent) {
         /* Look if a shown window is available to send the wakeup event. */
-        wakeup_window = SDL_find_active_window(_this);
+        SDL_Window *wakeup_window = SDL_find_active_window(_this);
         if (wakeup_window) {
             result = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, start, timeoutNS);
             if (result > 0) {
@@ -1455,6 +1484,7 @@ SDL_bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS)
         }
         SDL_DelayNS(delay);
     }
+#endif // SDL_PLATFORM_ANDROID
 }
 
 static SDL_bool SDL_CallEventWatchers(SDL_Event *event)

+ 15 - 10
src/render/SDL_render.c

@@ -29,6 +29,7 @@
 
 #ifdef SDL_PLATFORM_ANDROID
 #include "../core/android/SDL_android.h"
+#include "../video/android/SDL_androidevents.h"
 #endif
 
 /* as a courtesy to iOS apps, we don't try to draw when in the background, as
@@ -960,17 +961,19 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     int i, attempted = 0;
     SDL_PropertiesID new_props;
 
+#ifdef SDL_PLATFORM_ANDROID
+    if (Android_WaitActiveAndLockActivity() < 0) {
+        return NULL;
+    }
+#endif
+
     SDL_Renderer *renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(*renderer));
     if (!renderer) {
-        return NULL;
+        goto error;
     }
 
     SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, SDL_TRUE);
 
-#ifdef SDL_PLATFORM_ANDROID
-    Android_LockActivityMutexOnceRunning();
-#endif
-
     if ((!window && !surface) || (window && surface)) {
         SDL_InvalidParamError("window");
         goto error;
@@ -1135,14 +1138,16 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     return renderer;
 
 error:
-
-    SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, SDL_FALSE);
-
 #ifdef SDL_PLATFORM_ANDROID
     Android_UnlockActivityMutex();
 #endif
-    SDL_free(renderer->texture_formats);
-    SDL_free(renderer);
+
+    if (renderer) {
+        SDL_SetObjectValid(renderer, SDL_OBJECT_TYPE_RENDERER, SDL_FALSE);
+
+        SDL_free(renderer->texture_formats);
+        SDL_free(renderer);
+    }
     return NULL;
 
 #else

+ 141 - 62
src/video/android/SDL_androidevents.c

@@ -82,93 +82,172 @@ static void android_egl_context_backup(SDL_Window *window)
  * Android_ResumeSem and Android_PauseSem are signaled from Java_org_libsdl_app_SDLActivity_nativePause and Java_org_libsdl_app_SDLActivity_nativeResume
  */
 static SDL_bool Android_EventsInitialized;
+static SDL_bool Android_BlockOnPause = SDL_TRUE;
 static SDL_bool Android_Paused;
 static SDL_bool Android_PausedAudio;
-static Sint32 Android_PausedWaitTime = -1;
+static SDL_bool Android_Destroyed;
 
 void Android_InitEvents(void)
 {
     if (!Android_EventsInitialized) {
-        if (SDL_GetHintBoolean(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, SDL_TRUE)) {
-            Android_PausedWaitTime = -1;
-        } else {
-            Android_PausedWaitTime = 100;
-        }
+        Android_BlockOnPause = SDL_GetHintBoolean(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, SDL_TRUE);
         Android_Paused = SDL_FALSE;
+        Android_Destroyed = SDL_FALSE;
         Android_EventsInitialized = SDL_TRUE;
     }
 }
 
-void Android_PumpEvents(void)
+static void Android_OnPause(void)
 {
-    if (Android_Paused) {
-        if (SDL_WaitSemaphoreTimeout(Android_ResumeSem, Android_PausedWaitTime) == 0) {
+    SDL_OnApplicationWillEnterBackground();
+    SDL_OnApplicationDidEnterBackground();
+
+    /* The semantics are that as soon as the enter background event
+     * has been queued, the app will block. The application should
+     * do any life cycle handling in an event filter while the event
+     * 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();
+    }
+#endif
 
-            Android_Paused = SDL_FALSE;
+    if (Android_BlockOnPause) {
+        /* We're blocking, also pause audio */
+        ANDROIDAUDIO_PauseDevices();
+        OPENSLES_PauseDevices();
+        AAUDIO_PauseDevices();
+        Android_PausedAudio = SDL_TRUE;
+    }
+
+    Android_Paused = SDL_TRUE;
+}
+
+static void Android_OnResume(void)
+{
+    Android_Paused = SDL_FALSE;
 
-            /* Android_ResumeSem was signaled */
-            SDL_OnApplicationWillEnterForeground();
+    SDL_OnApplicationWillEnterForeground();
 
-            if (Android_PausedAudio) {
-                ANDROIDAUDIO_ResumeDevices();
-                OPENSLES_ResumeDevices();
-                AAUDIO_ResumeDevices();
-            }
+    if (Android_PausedAudio) {
+        ANDROIDAUDIO_ResumeDevices();
+        OPENSLES_ResumeDevices();
+        AAUDIO_ResumeDevices();
+    }
 
 #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();
-            }
+    /* 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();
+    }
 #endif
 
-            /* Make sure SW Keyboard is restored when an app becomes foreground */
-            if (Android_Window) {
-                Android_RestoreScreenKeyboardOnResume(SDL_GetVideoDevice(), Android_Window);
-            }
+    /* Make sure SW Keyboard is restored when an app becomes foreground */
+    if (Android_Window) {
+        Android_RestoreScreenKeyboardOnResume(SDL_GetVideoDevice(), Android_Window);
+    }
+
+    SDL_OnApplicationDidEnterForeground();
+}
+
+static void Android_OnLowMemory(void)
+{
+    SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY);
+}
+
+static void Android_OnDestroy(void)
+{
+    /* Discard previous events. The user should have handled state storage
+     * in SDL_EVENT_WILL_ENTER_BACKGROUND. After nativeSendQuit() is called, no
+     * events other than SDL_EVENT_QUIT and SDL_EVENT_TERMINATING should fire */
+    SDL_FlushEvents(SDL_EVENT_FIRST, SDL_EVENT_LAST);
+    SDL_SendQuit();
+    SDL_SendAppEvent(SDL_EVENT_TERMINATING);
+
+    Android_Destroyed = SDL_TRUE;
+}
+
+static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event)
+{
+    switch (event) {
+    case SDL_ANDROID_LIFECYCLE_WAKE:
+        // Nothing to do, just return
+        break;
+    case SDL_ANDROID_LIFECYCLE_PAUSE:
+        Android_OnPause();
+        break;
+    case SDL_ANDROID_LIFECYCLE_RESUME:
+        Android_OnResume();
+        break;
+    case SDL_ANDROID_LIFECYCLE_LOWMEMORY:
+        Android_OnLowMemory();
+        break;
+    case SDL_ANDROID_LIFECYCLE_DESTROY:
+        Android_OnDestroy();
+        break;
+    default:
+        break;
+    }
+}
 
-            SDL_OnApplicationDidEnterForeground();
+static Sint64 GetLifecycleEventTimeout(SDL_bool paused, Sint64 timeoutNS)
+{
+    if (Android_Paused) {
+        if (Android_BlockOnPause) {
+            timeoutNS = -1;
+        } else if (timeoutNS == 0) {
+            timeoutNS = SDL_MS_TO_NS(100);
         }
-    } else {
-        if (SDL_TryWaitSemaphore(Android_PauseSem) == 0) {
-
-            /* Android_PauseSem was signaled */
-            SDL_OnApplicationWillEnterBackground();
-            SDL_OnApplicationDidEnterBackground();
-
-            /* Make sure we handle potentially multiple pause/resume sequences */
-            while (SDL_GetSemaphoreValue(Android_PauseSem) > 0) {
-                SDL_WaitSemaphore(Android_ResumeSem);
-                SDL_WaitSemaphore(Android_PauseSem);
-            }
-
-            /* The semantics are that as soon as the enter background event
-             * has been queued, the app will block. The application should
-             * do any life cycle handling in an event filter while the event
-             * 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();
-            }
-#endif
-            if (Android_PausedWaitTime < 0) {
-                /* We're blocking, also pause audio */
-                ANDROIDAUDIO_PauseDevices();
-                OPENSLES_PauseDevices();
-                AAUDIO_PauseDevices();
-                Android_PausedAudio = SDL_TRUE;
-            }
-
-            Android_Paused = SDL_TRUE;
+    }
+    return timeoutNS;
+}
+
+void Android_PumpEvents(Sint64 timeoutNS)
+{
+    SDL_AndroidLifecycleEvent event;
+    SDL_bool paused = Android_Paused;
+
+    while (Android_WaitLifecycleEvent(&event, GetLifecycleEventTimeout(paused, timeoutNS))) {
+        Android_HandleLifecycleEvent(event);
+
+        switch (event) {
+        case SDL_ANDROID_LIFECYCLE_WAKE:
+            // Finish handling events quickly if we're not paused
+            timeoutNS = 0;
+            break;
+        case SDL_ANDROID_LIFECYCLE_PAUSE:
+            // Finish handling events at the current timeout and return to process events one more time before blocking.
+            break;
+        case SDL_ANDROID_LIFECYCLE_RESUME:
+            // Finish handling events at the resume state timeout
+            paused = SDL_FALSE;
+            break;
+        default:
+            break;
         }
     }
 }
 
+int Android_WaitActiveAndLockActivity(void)
+{
+    while (Android_Paused && !Android_Destroyed) {
+        Android_PumpEvents(-1);
+    }
+
+    if (Android_Destroyed) {
+        SDL_SetError("Android activity has been destroyed");
+        return -1;
+    }
+
+    Android_LockActivityMutex();
+    return 0;
+}
+
 void Android_QuitEvents(void)
 {
     Android_EventsInitialized = SDL_FALSE;

+ 2 - 3
src/video/android/SDL_androidevents.h

@@ -20,8 +20,7 @@
 */
 #include "SDL_internal.h"
 
-#include "SDL_androidvideo.h"
-
 extern void Android_InitEvents(void);
-extern void Android_PumpEvents(void);
+extern void Android_PumpEvents(Sint64 timeoutNS);
+extern int Android_WaitActiveAndLockActivity(void);
 extern void Android_QuitEvents(void);

+ 4 - 1
src/video/android/SDL_androidgl.c

@@ -28,6 +28,7 @@
 #include "SDL_androidwindow.h"
 
 #include "SDL_androidvideo.h"
+#include "SDL_androidevents.h"
 #include "SDL_androidgl.h"
 #include "../../core/android/SDL_android.h"
 
@@ -48,7 +49,9 @@ SDL_GLContext Android_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *win
 {
     SDL_GLContext ret;
 
-    Android_LockActivityMutexOnceRunning();
+    if (Android_WaitActiveAndLockActivity() < 0) {
+        return NULL;
+    }
 
     ret = SDL_EGL_CreateContext(_this, window->internal->egl_surface);
 

+ 4 - 1
src/video/android/SDL_androidwindow.c

@@ -29,6 +29,7 @@
 #include "../../core/android/SDL_android.h"
 
 #include "SDL_androidvideo.h"
+#include "SDL_androidevents.h"
 #include "SDL_androidwindow.h"
 
 
@@ -40,7 +41,9 @@ int Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
     SDL_WindowData *data;
     int retval = 0;
 
-    Android_LockActivityMutexOnceRunning();
+    if (Android_WaitActiveAndLockActivity() < 0) {
+        return -1;
+    }
 
     if (Android_Window) {
         retval = SDL_SetError("Android only supports one window");