| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_internal.h" |
| |
| #ifdef SDL_VIDEO_DRIVER_ANDROID |
| |
| #include "SDL_androidevents.h" |
| #include "SDL_androidkeyboard.h" |
| #include "SDL_androidwindow.h" |
| #include "../SDL_sysvideo.h" |
| #include "../../events/SDL_events_c.h" |
| |
| #include "../../audio/aaudio/SDL_aaudio.h" |
| #include "../../audio/openslES/SDL_openslES.h" |
| |
| |
| #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); |
| } |
| } |
| |
| 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(); |
| |
| // 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 |
| |
| /* |
| * Android_ResumeSem and Android_PauseSem are signaled from Java_org_libsdl_app_SDLActivity_nativePause and Java_org_libsdl_app_SDLActivity_nativeResume |
| */ |
| static bool Android_EventsInitialized; |
| static bool Android_BlockOnPause = true; |
| static bool Android_Paused; |
| static bool Android_PausedAudio; |
| static bool Android_Destroyed; |
| |
| void Android_InitEvents(void) |
| { |
| if (!Android_EventsInitialized) { |
| Android_BlockOnPause = SDL_GetHintBoolean(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, true); |
| Android_Paused = false; |
| Android_Destroyed = false; |
| Android_EventsInitialized = true; |
| } |
| } |
| |
| static void Android_PauseAudio(void) |
| { |
| OPENSLES_PauseDevices(); |
| AAUDIO_PauseDevices(); |
| Android_PausedAudio = true; |
| } |
| |
| static void Android_ResumeAudio(void) |
| { |
| if (Android_PausedAudio) { |
| OPENSLES_ResumeDevices(); |
| AAUDIO_ResumeDevices(); |
| Android_PausedAudio = false; |
| } |
| } |
| |
| 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 |
| Android_PauseAudio(); |
| } |
| |
| Android_Paused = true; |
| } |
| |
| static void Android_OnResume(void) |
| { |
| Android_Paused = false; |
| |
| SDL_OnApplicationWillEnterForeground(); |
| |
| Android_ResumeAudio(); |
| |
| #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 |
| |
| SDL_OnApplicationDidEnterForeground(); |
| } |
| |
| static void Android_OnLowMemory(void) |
| { |
| SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY); |
| } |
| |
| static void Android_OnDestroy(void) |
| { |
| // Make sure we unblock any audio processing before we quit |
| Android_ResumeAudio(); |
| |
| /* 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 = 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(bool paused, Sint64 timeoutNS) |
| { |
| if (Android_Paused) { |
| 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; |
| bool paused = Android_Paused; |
| |
| while (!Android_Destroyed && |
| 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 = false; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| bool Android_WaitActiveAndLockActivity(void) |
| { |
| /* 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); |
| } |
| |
| while (Android_Paused && !Android_Destroyed) { |
| Android_PumpEvents(-1); |
| } |
| |
| if (Android_Destroyed) { |
| SDL_SetError("Android activity has been destroyed"); |
| return false; |
| } |
| |
| Android_LockActivityMutex(); |
| return true; |
| } |
| |
| void Android_QuitEvents(void) |
| { |
| Android_EventsInitialized = false; |
| } |
| |
| #endif // SDL_VIDEO_DRIVER_ANDROID |