Android: implement safe areas margins

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ø <tor.arne.vestbo@qt.io>
This commit is contained in:
Assam Boudjelthia 2024-11-28 18:13:21 +02:00
parent b63e274a8a
commit c2b94a15c1
3 changed files with 104 additions and 12 deletions

View File

@ -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

View File

@ -12,6 +12,7 @@
#include <qguiapplication.h>
#include <qpa/qwindowsysteminterface.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qjnihelpers_p.h>
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<int>("left"),
insets.getField<int>("top"),
insets.getField<int>("right"),
insets.getField<int>("bottom"));
}
for (QWindow *window : qGuiApp->topLevelWindows()) {
if (!window->handle())
continue;
auto *pWindow = static_cast<QAndroidPlatformWindow *>(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<QMutex> QAndroidPlatformWindow::destructionGuard()
bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env)
{
if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtWindow>::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<QtJniTypes::QtWindow>::className();
return false;

View File

@ -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<QMutex> destructionGuard();
};