Revision control

Copy as Markdown

// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qandroidplatformwindow.h"
#include "androidbackendregister.h"
#include "qandroidplatformscreen.h"
#include "androidjnimain.h"
#include <qguiapplication.h>
#include <qpa/qwindowsysteminterface.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qjnihelpers_p.h>
#include <optional>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
Q_DECLARE_JNI_CLASS(QtWindowInterface, "org/qtproject/qt/android/QtWindowInterface")
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")
Q_DECLARE_JNI_CLASS(QtWindowInsetsController, "org/qtproject/qt/android/QtWindowInsetsController")
QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
: QPlatformWindow(window), m_nativeQtWindow(nullptr),
m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr),
m_androidSurfaceObject(nullptr)
{
if (window->surfaceType() == QSurface::RasterSurface)
window->setSurfaceType(QSurface::OpenGLSurface);
}
void QAndroidPlatformWindow::initialize()
{
if (isEmbeddingContainer())
return;
QWindow *window = QPlatformWindow::window();
if (parent()) {
QAndroidPlatformWindow *androidParent = static_cast<QAndroidPlatformWindow*>(parent());
if (!androidParent->isEmbeddingContainer())
m_nativeParentQtWindow = androidParent->nativeWindow();
}
AndroidBackendRegister *reg = QtAndroid::backendRegister();
QtJniTypes::QtInputConnectionListener listener =
reg->callInterface<QtJniTypes::QtInputInterface, QtJniTypes::QtInputConnectionListener>(
"getInputConnectionListener");
m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>(
QNativeInterface::QAndroidApplication::context(),
isForeignWindow(), m_nativeParentQtWindow, listener);
m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
// the surfaceType is overwritten in QAndroidPlatformOpenGLWindow ctor so let's save
// the fact that it's a raster window for now
m_isRaster = window->surfaceType() == QSurface::RasterSurface;
// the following is in relation to the virtual geometry
const bool forceMaximize = window->windowStates() & (Qt::WindowMaximized | Qt::WindowFullScreen);
const QRect nativeScreenGeometry = platformScreen()->availableGeometry();
if (forceMaximize) {
setGeometry(nativeScreenGeometry);
} else {
const QRect requestedNativeGeometry = QHighDpi::toNativePixels(window->geometry(), window);
const QRect availableDeviceIndependentGeometry = (window->parent())
? window->parent()->geometry()
: QHighDpi::fromNativePixels(nativeScreenGeometry, window);
// initialGeometry returns in native pixels
const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
availableDeviceIndependentGeometry.height());
setGeometry(finalNativeGeometry);
}
if (window->isTopLevel())
platformScreen()->addWindow(this);
static bool ok = false;
static const int value = qEnvironmentVariableIntValue("QT_ANDROID_SURFACE_CONTAINER_TYPE", &ok);
if (ok) {
static const SurfaceContainer type = static_cast<SurfaceContainer>(value);
if (type == SurfaceContainer::SurfaceView || type == SurfaceContainer::TextureView)
m_surfaceContainerType = type;
} else if (platformScreen()->windows().size() <= 1) {
// TODO should handle case where this changes at runtime -> need to change existing window
// into TextureView (or perhaps not, if the parent window would be SurfaceView, as long as
// onTop was false it would stay below the children)
m_surfaceContainerType = SurfaceContainer::SurfaceView;
}
qCDebug(lcQpaWindow) << "Window" << m_nativeViewId << "using surface container type"
<< static_cast<int>(m_surfaceContainerType);
}
QAndroidPlatformWindow::~QAndroidPlatformWindow()
{
const auto guard = destructionGuard();
if (window()->isTopLevel())
platformScreen()->removeWindow(this);
}
void QAndroidPlatformWindow::lower()
{
if (m_nativeParentQtWindow.isValid()) {
m_nativeParentQtWindow.callMethod<void>("bringChildToBack", nativeViewId());
return;
}
platformScreen()->lower(this);
}
void QAndroidPlatformWindow::raise()
{
if (m_nativeParentQtWindow.isValid()) {
m_nativeParentQtWindow.callMethod<void>("bringChildToFront", nativeViewId());
return;
}
applySystemUi(window()->windowStates(), window()->flags());
platformScreen()->raise(this);
}
QMargins QAndroidPlatformWindow::safeAreaMargins() const
{
return m_safeAreaMargins;
}
void QAndroidPlatformWindow::setSafeAreaMargins(const QMargins safeMargins)
{
m_safeAreaMargins = safeMargins;
}
void QAndroidPlatformWindow::setGeometry(const QRect &rect)
{
if (!isEmbeddingContainer()) {
Q_ASSERT(m_nativeQtWindow.isValid());
jint x = 0;
jint y = 0;
jint w = -1;
jint h = -1;
if (!rect.isNull()) {
x = rect.x();
y = rect.y();
w = rect.width();
h = rect.height();
}
m_nativeQtWindow.callMethod<void>("setGeometry", x, y, w, h);
}
QWindowSystemInterface::handleGeometryChange(window(), rect);
}
void QAndroidPlatformWindow::setVisible(bool visible)
{
if (isEmbeddingContainer())
return;
if (window()->isTopLevel()) {
if (!visible && window() == qGuiApp->focusWindow()) {
platformScreen()->topVisibleWindowChanged();
} else {
const Qt::WindowStates states = window()->windowStates();
const Qt::WindowFlags flags = window()->flags();
applySystemUi(states, flags);
if (states & Qt::WindowFullScreen || flags & Qt::ExpandedClientAreaHint)
setGeometry(platformScreen()->geometry());
else if (states & Qt::WindowMaximized)
setGeometry(platformScreen()->availableGeometry());
requestActivateWindow();
}
}
m_nativeQtWindow.callMethod<void>("setVisible", visible);
if (geometry().isEmpty() || screen()->availableGeometry().isEmpty())
return;
QPlatformWindow::setVisible(visible);
}
void QAndroidPlatformWindow::setWindowState(Qt::WindowStates state)
{
QPlatformWindow::setWindowState(state);
if (window()->isVisible())
applySystemUi(state, window()->flags());
}
void QAndroidPlatformWindow::setWindowFlags(Qt::WindowFlags flags)
{
QPlatformWindow::setWindowFlags(flags);
if (window()->isVisible())
applySystemUi(window()->windowStates(), flags);
}
void QAndroidPlatformWindow::setParent(const QPlatformWindow *window)
{
using namespace QtJniTypes;
if (window) {
auto androidWindow = static_cast<const QAndroidPlatformWindow*>(window);
if (androidWindow->isEmbeddingContainer())
return;
// If we were a top level window, remove from screen
if (!m_nativeParentQtWindow.isValid())
platformScreen()->removeWindow(this);
const QtWindow parentWindow = androidWindow->nativeWindow();
// If this was a child window of another window, the java method takes care of that
m_nativeQtWindow.callMethod<void, QtWindow>("setParent", parentWindow.object());
m_nativeParentQtWindow = parentWindow;
} else if (QtAndroid::isQtApplication()) {
m_nativeQtWindow.callMethod<void, QtWindow>("setParent", nullptr);
m_nativeParentQtWindow = QJniObject();
platformScreen()->addWindow(this);
}
}
WId QAndroidPlatformWindow::winId() const
{
return m_nativeQtWindow.isValid() ? reinterpret_cast<WId>(m_nativeQtWindow.object()) : 0L;
}
QAndroidPlatformScreen *QAndroidPlatformWindow::platformScreen() const
{
return static_cast<QAndroidPlatformScreen *>(window()->screen()->handle());
}
void QAndroidPlatformWindow::propagateSizeHints()
{
//shut up warning from default implementation
}
void QAndroidPlatformWindow::requestActivateWindow()
{
if (blockedByModal())
return;
raise();
// For child windows, raise() only updates "Z-order", so focus must be requested explicitly.
if (m_nativeParentQtWindow.isValid())
QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
}
namespace {
// The extended system UI modes Qt can take ownership of. Plain "normal" is not
// a mode Qt owns, it is the absence of ownership, represented by an empty
// optional, so window state the app manages itself is never touched by Qt.
enum class SystemUiMode { Expanded, FullScreen };
std::optional<SystemUiMode> systemUiModeFor(Qt::WindowStates states, Qt::WindowFlags flags)
{
if (states & Qt::WindowFullScreen)
return SystemUiMode::FullScreen;
if (flags & Qt::ExpandedClientAreaHint)
return SystemUiMode::Expanded;
return std::nullopt;
}
// The mode Qt currently owns for the activity window, only ever touched on the
// gui thread. Empty means Qt owns nothing and leaves the system UI to the app.
std::optional<SystemUiMode> g_ownedSystemUiMode;
void applySystemUiMode(std::optional<SystemUiMode> mode)
{
g_ownedSystemUiMode = mode;
auto iface = qGuiApp->nativeInterface<QNativeInterface::QAndroidApplication>();
iface->runOnAndroidMainThread([mode, iface]() {
using namespace QtJniTypes;
auto activity = iface->context().object<Activity>();
if (mode == SystemUiMode::FullScreen)
QtWindowInsetsController::callStaticMethod("showFullScreen", activity);
else if (mode == SystemUiMode::Expanded)
QtWindowInsetsController::callStaticMethod("showExpanded", activity);
else
QtWindowInsetsController::callStaticMethod("showNormal", activity);
});
}
} // unnamed namespace
void QAndroidPlatformWindow::applySystemUi(Qt::WindowStates states, Qt::WindowFlags flags)
{
const bool isNonRegularWindow = flags & (Qt::Popup | Qt::Dialog | Qt::Sheet) & ~Qt::Window;
if (isNonRegularWindow)
return;
const std::optional<SystemUiMode> mode = systemUiModeFor(states, flags);
if (mode != g_ownedSystemUiMode)
applySystemUiMode(mode);
}
void QAndroidPlatformWindow::applySystemUiNative(JNIEnv *env, jobject object)
{
Q_UNUSED(env)
Q_UNUSED(object)
if (!qGuiApp)
return;
// Some lifecycle events reset window state on the OS side. Re-assert the
// mode Qt owns, if any. When Qt owns nothing the app keeps its own state.
QMetaObject::invokeMethod(qGuiApp, [] {
if (g_ownedSystemUiMode)
applySystemUiMode(g_ownedSystemUiMode);
});
}
void QAndroidPlatformWindow::updateFocusedEditText()
{
m_nativeQtWindow.callMethod<void>("updateFocusedEditText");
}
bool QAndroidPlatformWindow::isExposed() const
{
return qApp->applicationState() > Qt::ApplicationHidden
&& window()->isVisible()
&& !window()->geometry().isEmpty();
}
void QAndroidPlatformWindow::applicationStateChanged(Qt::ApplicationState)
{
QRegion region;
if (isExposed())
region = QRect(QPoint(), geometry().size());
QWindowSystemInterface::handleExposeEvent(window(), region);
QWindowSystemInterface::flushWindowSystemEvents();
}
void QAndroidPlatformWindow::createSurface()
{
const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
const bool isOpaque = !format().hasAlpha() && qFuzzyCompare(window()->opacity(), qreal(1.0));
m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, 32, isOpaque,
m_surfaceContainerType);
m_androidSurfaceCreated = true;
}
void QAndroidPlatformWindow::destroySurface()
{
if (m_androidSurfaceCreated) {
m_nativeQtWindow.callMethod<void>("destroySurface");
m_androidSurfaceCreated = false;
}
}
void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
{
lockSurface();
const bool surfaceIsValid = surface.isValid();
qCDebug(lcQpaWindow) << "onSurfaceChanged(): valid Surface received" << surfaceIsValid;
m_androidSurfaceObject = surface;
if (surfaceIsValid) {
// wait until we have a valid surface to draw into
m_surfaceWaitCondition.wakeOne();
} else {
clearSurface();
}
sendExpose();
unlockSurface();
}
void QAndroidPlatformWindow::sendExpose() const
{
QRect availableGeometry = screen()->availableGeometry();
if (!geometry().isNull() && !availableGeometry.isNull()) {
QWindowSystemInterface::handleExposeEvent(window(),
QRegion(QRect(QPoint(), geometry().size())));
}
}
bool QAndroidPlatformWindow::blockedByModal() const
{
QWindow *modalWindow = QGuiApplication::modalWindow();
return modalWindow && modalWindow != window();
}
bool QAndroidPlatformWindow::isEmbeddingContainer() const
{
// Returns true if the window is a wrapper for a foreign window solely to allow embedding Qt
// into a native Android app, in which case we should not try to control it more than we "need" to
return !QtAndroid::isQtApplication() && window()->isTopLevel();
}
void QAndroidPlatformWindow::setSurface(JNIEnv *env, jobject object, jint windowId,
QtJniTypes::Surface surface)
{
Q_UNUSED(env)
Q_UNUSED(object)
if (!qGuiApp)
return;
const QList<QWindow*> windows = qGuiApp->allWindows();
for (QWindow * window : windows) {
if (!window->handle())
continue;
QAndroidPlatformWindow *platformWindow =
static_cast<QAndroidPlatformWindow *>(window->handle());
const auto guard = platformWindow->destructionGuard();
if (!platformWindow->m_androidSurfaceCreated)
continue;
if (platformWindow->nativeViewId() == windowId)
platformWindow->onSurfaceChanged(surface);
}
}
void QAndroidPlatformWindow::windowFocusChanged(JNIEnv *env, jobject object,
jboolean focus, jint windowId)
{
Q_UNUSED(env)
Q_UNUSED(object)
QWindow* window = QtAndroid::windowFromId(windowId);
if (!window) {
qCWarning(lcQpaWindow,
"windowFocusChanged event received for non-existing window %d", windowId);
return;
}
if (focus) {
QWindowSystemInterface::handleFocusWindowChanged(window);
} else if (!focus && window == qGuiApp->focusWindow()) {
// Clear focus if current window has lost focus
QWindowSystemInterface::handleFocusWindowChanged(nullptr);
}
}
void QAndroidPlatformWindow::safeAreaMarginsChanged(JNIEnv *env, jobject object,
QtJniTypes::Insets insets, jint id)
{
Q_UNUSED(env)
Q_UNUSED(object)
if (!qGuiApp)
return;
if (!insets.isValid())
return;
QAndroidPlatformWindow *pWindow = nullptr;
for (QWindow *window : qGuiApp->allWindows()) {
if (!window->handle())
continue;
QAndroidPlatformWindow *pw = static_cast<QAndroidPlatformWindow *>(window->handle());
if (pw->nativeViewId() == id) {
pWindow = pw;
break;
}
}
if (!pWindow)
return;
QMargins safeMargins = QMargins(
insets.getField<int>("left"),
insets.getField<int>("top"),
insets.getField<int>("right"),
insets.getField<int>("bottom"));
if (safeMargins != pWindow->safeAreaMargins()) {
pWindow->setSafeAreaMargins(safeMargins);
QWindowSystemInterface::handleSafeAreaMarginsChanged(pWindow->window());
}
}
static void updateWindows(JNIEnv *env, jobject object)
{
Q_UNUSED(env)
Q_UNUSED(object)
if (QGuiApplication::instance() != nullptr) {
const auto tlw = QGuiApplication::topLevelWindows();
for (QWindow *w : tlw) {
// Skip non-platform windows, e.g., offscreen windows.
if (!w->handle())
continue;
const QRect availableGeometry = w->screen()->availableGeometry();
const QRect geometry = w->geometry();
const bool isPositiveGeometry = (geometry.width() > 0 && geometry.height() > 0);
const bool isPositiveAvailableGeometry =
(availableGeometry.width() > 0 && availableGeometry.height() > 0);
if (isPositiveGeometry && isPositiveAvailableGeometry) {
const QRegion region = QRegion(QRect(QPoint(), w->geometry().size()));
QWindowSystemInterface::handleExposeEvent(w, region);
}
}
}
}
Q_DECLARE_JNI_NATIVE_METHOD(updateWindows)
/*
Due to calls originating from Android, it is possible for native methods to
try to manipulate any given instance of QAndroidPlatformWindow when it is
already being destroyed. So we use this to guard against that. It is called
in the destructor, and should also be called in any function registered to
be called from java that may touch an instance of QAndroidPlatformWindow.
*/
QMutexLocker<QMutex> QAndroidPlatformWindow::destructionGuard()
{
return QMutexLocker(&m_destructionMutex);
}
Q_CONSTINIT static QBasicAtomicInt g_surfacesCounter = Q_BASIC_ATOMIC_INITIALIZER(0);
int QAndroidPlatformWindow::surfacesCount()
{
return g_surfacesCounter.loadRelaxed();
}
void QAndroidPlatformWindow::incrementSurfacesCount()
{
g_surfacesCounter.fetchAndAddRelaxed(1);
}
void QAndroidPlatformWindow::decrementSurfacesCount()
{
int cur = g_surfacesCounter.loadRelaxed();
while (true) {
if (cur == 0)
return;
if (g_surfacesCounter.testAndSetRelaxed(cur, cur - 1))
return;
}
}
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_SCOPED_METHOD(safeAreaMarginsChanged, QAndroidPlatformWindow)
})) {
qCCritical(lcQpaWindow) << "RegisterNatives failed for"
<< QtJniTypes::Traits<QtJniTypes::QtWindow>::className();
return false;
}
if (!env.registerNativeMethods(
QtJniTypes::Traits<QtJniTypes::QtWindowInsetsController>::className(),
{
Q_JNI_NATIVE_SCOPED_METHOD(applySystemUiNative, QAndroidPlatformWindow)
})) {
qCCritical(lcQpaWindow) << "RegisterNatives failed for"
<< QtJniTypes::Traits<QtJniTypes::QtWindowInsetsController>::className();
return false;
}
return true;
}
QT_END_NAMESPACE