From c2b94a15c14f9278dfb28103957f1090f4c323b6 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Thu, 28 Nov 2024 18:13:21 +0200 Subject: [PATCH] Android: implement safe areas margins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rely on Android APIs (old and new) to calculate safe area margins, which takes into account the cutout regions and the system bars. Task-number: QTBUG-131519 Task-number: QTBUG-98989 Change-Id: Ie69e6c54df30c76e67d9ca96518e13f9898e6312 Reviewed-by: Tor Arne Vestbø --- .../org/qtproject/qt/android/QtWindow.java | 48 ++++++++++++++ .../android/qandroidplatformwindow.cpp | 62 +++++++++++++++---- .../android/qandroidplatformwindow.h | 6 ++ 3 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java index 26536956fe4..cb7a6404e80 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java @@ -6,11 +6,18 @@ package org.qtproject.qt.android; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; + +import android.graphics.Insets; + +import android.view.DisplayCutout; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; + +import android.os.Build; import java.util.HashMap; @@ -25,6 +32,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { private final QtInputConnection.QtInputConnectionListener m_inputConnectionListener; private static native void setSurface(int windowId, Surface surface); + private static native void safeAreaMarginsChanged(Insets insets); static native void windowFocusChanged(boolean hasFocus, int id); static native void updateWindows(); @@ -67,6 +75,46 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); m_gestureDetector.setIsLongpressEnabled(true); }); + + if (getContext() instanceof QtActivityBase) { + View decorView = ((Activity) context).getWindow().getDecorView(); + decorView.setOnApplyWindowInsetsListener((view, insets) -> { + Insets safeInsets; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + int types = WindowInsets.Type.displayCutout() | WindowInsets.Type.systemBars(); + safeInsets = insets.getInsets(types); + } else { + int left = 0; + int top = 0; + int right = 0; + int bottom = 0; + + int visibility = view.getSystemUiVisibility(); + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + left = insets.getSystemWindowInsetLeft(); + top = insets.getSystemWindowInsetTop(); + right = insets.getSystemWindowInsetRight(); + bottom = insets.getSystemWindowInsetBottom(); + } + + // Android 9 and 10 emulators don't seem to be able + // to handle this, but let's have the logic here anyway + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + left = Math.max(left, cutout.getSafeInsetLeft()); + top = Math.max(top, cutout.getSafeInsetTop()); + right = Math.max(right, cutout.getSafeInsetRight()); + bottom = Math.max(bottom, cutout.getSafeInsetBottom()); + } + + safeInsets = Insets.of(left, top, right, bottom); + } + + safeAreaMarginsChanged(safeInsets); + + return insets; + }); + } } @UsedFromNativeCode diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp index b535ea78ed2..51e5f95605f 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.cpp +++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -21,6 +22,7 @@ Q_DECLARE_JNI_CLASS(QtWindowInterface, "org/qtproject/qt/android/QtWindowInterfa Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface") Q_DECLARE_JNI_CLASS(QtInputConnectionListener, "org/qtproject/qt/android/QtInputConnection$QtInputConnectionListener") +Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtWindowInterface") QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) : QPlatformWindow(window), m_nativeQtWindow(nullptr), @@ -126,13 +128,12 @@ void QAndroidPlatformWindow::raise() QMargins QAndroidPlatformWindow::safeAreaMargins() const { - if ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::ExpandedClientAreaHint)) { - QRect availableGeometry = platformScreen()->availableGeometry(); - return QMargins(availableGeometry.left(), availableGeometry.top(), - availableGeometry.right(), availableGeometry.bottom()); - } else { - return QPlatformWindow::safeAreaMargins(); - } + return m_safeAreaMargins; +} + +void QAndroidPlatformWindow::setSafeAreaMargins(const QMargins safeMargins) +{ + m_safeAreaMargins = safeMargins; } void QAndroidPlatformWindow::setGeometry(const QRect &rect) @@ -382,6 +383,42 @@ void QAndroidPlatformWindow::windowFocusChanged(JNIEnv *env, jobject object, } } +void QAndroidPlatformWindow::safeAreaMarginsChanged(JNIEnv *env, jobject object, QtJniTypes::Insets insets) +{ + Q_UNUSED(env) + Q_UNUSED(object) + + if (!qGuiApp) + return; + + const auto tlw = qGuiApp->topLevelWindows(); + if (tlw.isEmpty()) + return; + + QMargins safeMargins; + if (insets.isValid()) { + safeMargins = QMargins( + insets.getField("left"), + insets.getField("top"), + insets.getField("right"), + insets.getField("bottom")); + } + + for (QWindow *window : qGuiApp->topLevelWindows()) { + if (!window->handle()) + continue; + + auto *pWindow = static_cast(window->handle()); + if (!pWindow) + return; + + if (safeMargins != pWindow->safeAreaMargins()) { + pWindow->setSafeAreaMargins(safeMargins); + QWindowSystemInterface::handleSafeAreaMarginsChanged(window); + } + } +} + static void updateWindows(JNIEnv *env, jobject object) { Q_UNUSED(env) @@ -425,11 +462,12 @@ QMutexLocker QAndroidPlatformWindow::destructionGuard() bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env) { if (!env.registerNativeMethods(QtJniTypes::Traits::className(), - { - Q_JNI_NATIVE_METHOD(updateWindows), - Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow), - Q_JNI_NATIVE_SCOPED_METHOD(windowFocusChanged, QAndroidPlatformWindow) - })) { + { + Q_JNI_NATIVE_METHOD(updateWindows), + Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow), + Q_JNI_NATIVE_SCOPED_METHOD(windowFocusChanged, QAndroidPlatformWindow), + Q_JNI_NATIVE_SCOPED_METHOD(safeAreaMarginsChanged, QAndroidPlatformWindow) + })) { qCCritical(lcQpaWindow) << "RegisterNatives failed for" << QtJniTypes::Traits::className(); return false; diff --git a/src/plugins/platforms/android/qandroidplatformwindow.h b/src/plugins/platforms/android/qandroidplatformwindow.h index c282adb9b0e..39010879608 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.h +++ b/src/plugins/platforms/android/qandroidplatformwindow.h @@ -20,6 +20,7 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow) Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow") Q_DECLARE_JNI_CLASS(Surface, "android/view/Surface") +Q_DECLARE_JNI_CLASS(Insets, "android/graphics/Insets") class QAndroidPlatformScreen; @@ -53,6 +54,7 @@ public: QAndroidPlatformScreen *platformScreen() const; QMargins safeAreaMargins() const override; + void setSafeAreaMargins(const QMargins safeMargins); void propagateSizeHints() override; void requestActivateWindow() override; @@ -99,11 +101,15 @@ protected: QMutex m_surfaceMutex; QMutex m_destructionMutex; + QMargins m_safeAreaMargins; + private: static void setSurface(JNIEnv *env, jobject obj, jint windowId, QtJniTypes::Surface surface); Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setSurface) static void windowFocusChanged(JNIEnv *env, jobject object, jboolean focus, jint windowId); Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(windowFocusChanged) + static void safeAreaMarginsChanged(JNIEnv *env, jobject obj, QtJniTypes::Insets insets); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(safeAreaMarginsChanged) [[nodiscard]] QMutexLocker destructionGuard(); };