From c601120883f8e1d45148bc366632832fd61c81b4 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 Jul 2024 12:43:44 -0700 Subject: [PATCH] Handle all Android lifecycle events on the main thread This restructuring also allows us to wait efficiently in SDL_WaitEvent() on Android --- src/core/android/SDL_android.c | 185 ++++++++++++++-------- src/core/android/SDL_android.h | 33 ++-- src/events/SDL_events.c | 38 ++++- src/render/SDL_render.c | 25 +-- src/video/android/SDL_androidevents.c | 219 ++++++++++++++++++-------- src/video/android/SDL_androidevents.h | 5 +- src/video/android/SDL_androidgl.c | 5 +- src/video/android/SDL_androidwindow.c | 5 +- 8 files changed, 352 insertions(+), 163 deletions(-) diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index f6d48425c..1e42cd00f 100644 --- a/src/core/android/SDL_android.c +++ b/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,18 +897,101 @@ 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; +} + +static void RemoveLifecycleEvent(int index) +{ + 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_SendLifecycleEvent(SDL_AndroidLifecycleEvent event) +{ + 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); +} + +SDL_bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS) +{ + SDL_bool got_event = SDL_FALSE; + + 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; } -/* Lock / Unlock Mutex */ void Android_LockActivityMutex(void) { SDL_LockMutex(Android_ActivityMutex); @@ -917,24 +1002,15 @@ void Android_UnlockActivityMutex(void) SDL_UnlockMutex(Android_ActivityMutex); } -/* Lock the Mutex when the Activity is in its 'Running' state */ -void Android_LockActivityMutexOnceRunning(void) +/* Drop file */ +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)( + JNIEnv *env, jclass jcls, + jstring filename) { - int pauseSignaled = 0; - int resumeSignaled = 0; - -retry: - - SDL_LockMutex(Android_ActivityMutex); - - pauseSignaled = SDL_GetSemaphoreValue(Android_PauseSem); - resumeSignaled = SDL_GetSemaphoreValue(Android_ResumeSem); - - if (pauseSignaled > resumeSignaled) { - SDL_UnlockMutex(Android_ActivityMutex); - SDL_Delay(50); - goto retry; - } + 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)( diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index fea6c426a..9461ea0c5 100644 --- a/src/core/android/SDL_android.h +++ b/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 diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index a8fd5450f..233b245c1 100644 --- a/src/events/SDL_events.c +++ b/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) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 7ebee08c9..a5cc25ec2 100644 --- a/src/render/SDL_render.c +++ b/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 diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c index aff92e6da..ef8957c0b 100644 --- a/src/video/android/SDL_androidevents.c +++ b/src/video/android/SDL_androidevents.c @@ -82,91 +82,170 @@ 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) +{ + 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 + + 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; + + SDL_OnApplicationWillEnterForeground(); + + 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(); + } +#endif + + /* 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; + } +} + +static Sint64 GetLifecycleEventTimeout(SDL_bool paused, Sint64 timeoutNS) { if (Android_Paused) { - if (SDL_WaitSemaphoreTimeout(Android_ResumeSem, Android_PausedWaitTime) == 0) { - - Android_Paused = SDL_FALSE; - - /* Android_ResumeSem was signaled */ - SDL_OnApplicationWillEnterForeground(); - - 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(); - } -#endif - - /* Make sure SW Keyboard is restored when an app becomes foreground */ - if (Android_Window) { - Android_RestoreScreenKeyboardOnResume(SDL_GetVideoDevice(), Android_Window); - } - - SDL_OnApplicationDidEnterForeground(); - } - } 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; + if (Android_BlockOnPause) { + timeoutNS = -1; + } else if (timeoutNS == 0) { + timeoutNS = SDL_MS_TO_NS(100); } } + 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) diff --git a/src/video/android/SDL_androidevents.h b/src/video/android/SDL_androidevents.h index 884d6f59f..283c6840e 100644 --- a/src/video/android/SDL_androidevents.h +++ b/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); diff --git a/src/video/android/SDL_androidgl.c b/src/video/android/SDL_androidgl.c index d310fbb8a..3fdddf5b3 100644 --- a/src/video/android/SDL_androidgl.c +++ b/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); diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c index e27bf8c25..ad777dfb4 100644 --- a/src/video/android/SDL_androidwindow.c +++ b/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");