SDL: Added support for inset handling on Android 15

From e91c37f4dd1933c44acc3d5eb4b58ae1420fbe85 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 30 Dec 2024 12:23:00 -0800
Subject: [PATCH] Added support for inset handling on Android 15

---
 android-project/app/build.gradle              |  4 +--
 .../main/java/org/libsdl/app/SDLActivity.java |  4 +++
 docs/README-android.md                        | 35 ++++++++++---------
 src/core/android/SDL_android.c                |  4 +--
 src/video/android/SDL_androidvideo.c          | 16 +++++++++
 src/video/android/SDL_androidvideo.h          |  5 +++
 src/video/android/SDL_androidwindow.c         |  2 ++
 7 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/android-project/app/build.gradle b/android-project/app/build.gradle
index 514e594a4c1f2..a95bb8aff54eb 100644
--- a/android-project/app/build.gradle
+++ b/android-project/app/build.gradle
@@ -6,10 +6,10 @@ def buildWithCMake = project.hasProperty('BUILD_WITH_CMAKE');
 
 android {
     namespace "org.libsdl.app"
-    compileSdkVersion 34
+    compileSdkVersion 35
     defaultConfig {
         minSdkVersion 19
-        targetSdkVersion 34
+        targetSdkVersion 35
         versionCode 1
         versionName "1.0"
         externalNativeBuild {
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index 6067fa74dcafc..7458b22d7ed78 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -928,6 +928,10 @@ public void handleMessage(Message msg) {
                             if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) {
                                 window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                             }
+                            if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */ &&
+                                Build.VERSION.SDK_INT < 35 /* Android 15 */) {
+                                SDLActivity.onNativeInsetsChanged(0, 0, 0, 0);
+                            }
                         }
                     } else {
                         Log.e(TAG, "error handling message, getContext() returned no Activity");
diff --git a/docs/README-android.md b/docs/README-android.md
index 722a886b1eb89..606eba6152570 100644
--- a/docs/README-android.md
+++ b/docs/README-android.md
@@ -10,7 +10,7 @@ The rest of this README covers the Android gradle style build process.
 Requirements
 ================================================================================
 
-Android SDK (version 34 or later)
+Android SDK (version 35 or later)
 https://developer.android.com/sdk/index.html
 
 Android NDK r15c or later
@@ -316,6 +316,17 @@ You can control activity re-creation (eg. onCreate()) behaviour. This allows you
 to choose whether to keep or re-initialize java and native static datas, see
 SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY in SDL_hints.h.
 
+
+Insets and Safe Areas
+================================================================================
+
+As of Android 15, SDL windows cover the entire screen, extending under notches
+and system bars. The OS expects you to take those into account when displaying
+content and SDL provides the function SDL_GetWindowSafeArea() so you know what
+area is available for interaction. Outside of the safe area can be potentially
+covered by system bars or used by OS gestures.
+
+
 Mouse / Touch events
 ================================================================================
 
@@ -325,6 +336,7 @@ To enable/disable this behavior, see SDL_hints.h:
 - SDL_HINT_TOUCH_MOUSE_EVENTS
 - SDL_HINT_MOUSE_TOUCH_EVENTS
 
+
 Misc
 ================================================================================
 
@@ -334,6 +346,7 @@ before creating a window:
   SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
   SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
 
+
 Threads and the Java VM
 ================================================================================
 
@@ -359,6 +372,7 @@ in your native thread.
 see:
 https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class
 
+
 Using STL
 ================================================================================
 
@@ -526,15 +540,6 @@ The Tegra Graphics Debugger is available from NVidia here:
 https://developer.nvidia.com/tegra-graphics-debugger
 
 
-Why is API level 19 the minimum required?
-================================================================================
-
-The latest NDK toolchain doesn't support targeting earlier than API level 19.
-As of this writing, according to https://www.composables.com/tools/distribution-chart
-about 99.7% of the Android devices accessing Google Play support API level 19 or
-higher (August 2023).
-
-
 A note regarding the use of the "dirty rectangles" rendering technique
 ================================================================================
 
@@ -545,12 +550,6 @@ This is caused by SDL's use of EGL as the support system to handle OpenGL ES/ES2
 contexts, in particular the use of the eglSwapBuffers function. As stated in the
 documentation for the function "The contents of ancillary buffers are always
 undefined after calling eglSwapBuffers".
-Setting the EGL_SWAP_BEHAVIOR attribute of the surface to EGL_BUFFER_PRESERVED
-is not possible for SDL as it requires EGL 1.4, available only on the API level
-17+, so the only workaround available on this platform is to redraw the entire
-screen each frame.
-
-Reference: http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html
 
 
 Ending your application
@@ -570,12 +569,14 @@ Don't call exit() as it stops the activity badly.
 NB: "Back button" can be handled as a SDL_EVENT_KEY_DOWN/UP events, with Keycode
 SDLK_AC_BACK, for any purpose.
 
+
 Known issues
 ================================================================================
 
 - The number of buttons reported for each joystick is hardcoded to be 36, which
 is the current maximum number of buttons Android can report.
 
+
 Building the SDL tests
 ================================================================================
 
@@ -651,4 +652,4 @@ There is also a convenience target which will build, install and start a test:
 cmake --build . --target build-install-start-testsprite

-Not all tests provide a GUI. For those, you can use adb logcat to read the output of stdout.
+Not all tests provide a GUI. For those, you can use adb logcat to read the output.
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index 2835ad3e212ba…419d4c29f146d 100644
— a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -1077,9 +1077,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
{
SDL_LockMutex(Android_ActivityMutex);

  • if (Android_Window) {
  •    SDL_SetWindowSafeAreaInsets(Android_Window, left, right, top, bottom);
    
  • }
  • Android_SetWindowSafeAreaInsets(left, right, top, bottom);

    SDL_UnlockMutex(Android_ActivityMutex);
    }
    diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c
    index ee8d889394884…ab360a11c44aa 100644
    — a/src/video/android/SDL_androidvideo.c
    +++ b/src/video/android/SDL_androidvideo.c
    @@ -63,6 +63,10 @@ static int Android_DeviceHeight = 0;
    static Uint32 Android_ScreenFormat = SDL_PIXELFORMAT_RGB565; // Default SurfaceView format, in case this is queried before being filled
    float Android_ScreenDensity = 1.0f;
    static float Android_ScreenRate = 0.0f;
    +int Android_SafeInsetLeft = 0;
    +int Android_SafeInsetRight = 0;
    +int Android_SafeInsetTop = 0;
    +int Android_SafeInsetBottom = 0;
    static SDL_SystemTheme Android_SystemTheme;

static bool Android_SuspendScreenSaver(SDL_VideoDevice *_this)
@@ -271,6 +275,18 @@ void Android_SendResize(SDL_Window *window)
}
}

+void Android_SetWindowSafeAreaInsets(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);
    
  • }
    +}

void Android_SetDarkMode(bool enabled)
{
SDL_VideoDevice *device = SDL_GetVideoDevice();
diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h
index baead920aefe1…bcc234f5d0af8 100644
— a/src/video/android/SDL_androidvideo.h
+++ b/src/video/android/SDL_androidvideo.h
@@ -29,6 +29,7 @@
extern void Android_SetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float density, float rate);
extern void Android_SetFormat(int format_wanted, int format_got);
extern void Android_SendResize(SDL_Window *window);
+extern void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom);
extern void Android_SetDarkMode(bool enabled);

// Private display data
@@ -42,5 +43,9 @@ struct SDL_VideoData
extern int Android_SurfaceWidth;
extern int Android_SurfaceHeight;
extern float Android_ScreenDensity;
+extern int Android_SafeInsetLeft;
+extern int Android_SafeInsetRight;
+extern int Android_SafeInsetTop;
+extern int Android_SafeInsetBottom;

#endif // SDL_androidvideo_h_
diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c
index f2a5757be703e…a4ac97c8a60fc 100644
— a/src/video/android/SDL_androidwindow.c
+++ b/src/video/android/SDL_androidwindow.c
@@ -93,6 +93,8 @@ bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
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;