Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidplatformwindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
9
10#include "androidjnimain.h"
11
12#include <qguiapplication.h>
13#include <qpa/qwindowsysteminterface.h>
14#include <private/qhighdpiscaling_p.h>
15#include <private/qjnihelpers_p.h>
16
17#include <optional>
18
20
21Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
22
23Q_DECLARE_JNI_CLASS(QtWindowInterface, "org/qtproject/qt/android/QtWindowInterface")
24Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface")
25Q_DECLARE_JNI_CLASS(QtInputConnectionListener,
26 "org/qtproject/qt/android/QtInputConnection$QtInputConnectionListener")
27Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtWindowInterface")
28Q_DECLARE_JNI_CLASS(QtWindowInsetsController, "org/qtproject/qt/android/QtWindowInsetsController")
29
30QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
31 : QPlatformWindow(window), m_nativeQtWindow(nullptr),
32 m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr),
33 m_androidSurfaceObject(nullptr)
34{
35 if (window->surfaceType() == QSurface::RasterSurface)
36 window->setSurfaceType(QSurface::OpenGLSurface);
37}
38
40{
42 return;
43
44 QWindow *window = QPlatformWindow::window();
45
46 if (parent()) {
47 QAndroidPlatformWindow *androidParent = static_cast<QAndroidPlatformWindow*>(parent());
48 if (!androidParent->isEmbeddingContainer())
49 m_nativeParentQtWindow = androidParent->nativeWindow();
50 }
51
53 QtJniTypes::QtInputConnectionListener listener =
54 reg->callInterface<QtJniTypes::QtInputInterface, QtJniTypes::QtInputConnectionListener>(
55 "getInputConnectionListener");
56
57 m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>(
58 QNativeInterface::QAndroidApplication::context(),
59 isForeignWindow(), m_nativeParentQtWindow, listener);
60 m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
61
62 // the surfaceType is overwritten in QAndroidPlatformOpenGLWindow ctor so let's save
63 // the fact that it's a raster window for now
64 m_isRaster = window->surfaceType() == QSurface::RasterSurface;
65
66 // the following is in relation to the virtual geometry
67 const bool forceMaximize = window->windowStates() & (Qt::WindowMaximized | Qt::WindowFullScreen);
68 const QRect nativeScreenGeometry = platformScreen()->availableGeometry();
69 if (forceMaximize) {
70 setGeometry(nativeScreenGeometry);
71 } else {
72 const QRect requestedNativeGeometry = QHighDpi::toNativePixels(window->geometry(), window);
73 const QRect availableDeviceIndependentGeometry = (window->parent())
74 ? window->parent()->geometry()
75 : QHighDpi::fromNativePixels(nativeScreenGeometry, window);
76 // initialGeometry returns in native pixels
77 const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
78 window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
79 availableDeviceIndependentGeometry.height());
80 setGeometry(finalNativeGeometry);
81 }
82
83 if (window->isTopLevel())
84 platformScreen()->addWindow(this);
85
86 static bool ok = false;
87 static const int value = qEnvironmentVariableIntValue("QT_ANDROID_SURFACE_CONTAINER_TYPE", &ok);
88 if (ok) {
89 static const SurfaceContainer type = static_cast<SurfaceContainer>(value);
92 } else if (platformScreen()->windows().size() <= 1) {
93 // TODO should handle case where this changes at runtime -> need to change existing window
94 // into TextureView (or perhaps not, if the parent window would be SurfaceView, as long as
95 // onTop was false it would stay below the children)
97 }
98 qCDebug(lcQpaWindow) << "Window" << m_nativeViewId << "using surface container type"
99 << static_cast<int>(m_surfaceContainerType);
100}
101
103{
104 const auto guard = destructionGuard();
105 if (window()->isTopLevel())
106 platformScreen()->removeWindow(this);
107}
108
109
111{
112 if (m_nativeParentQtWindow.isValid()) {
113 m_nativeParentQtWindow.callMethod<void>("bringChildToBack", nativeViewId());
114 return;
115 }
116 platformScreen()->lower(this);
117}
118
120{
121 if (m_nativeParentQtWindow.isValid()) {
122 m_nativeParentQtWindow.callMethod<void>("bringChildToFront", nativeViewId());
123 return;
124 }
125 applySystemUi(window()->windowStates(), window()->flags());
126 platformScreen()->raise(this);
127}
128
130{
131 return m_safeAreaMargins;
132}
133
134void QAndroidPlatformWindow::setSafeAreaMargins(const QMargins safeMargins)
135{
136 m_safeAreaMargins = safeMargins;
137}
138
139void QAndroidPlatformWindow::setGeometry(const QRect &rect)
140{
142 Q_ASSERT(m_nativeQtWindow.isValid());
143
144 jint x = 0;
145 jint y = 0;
146 jint w = -1;
147 jint h = -1;
148 if (!rect.isNull()) {
149 x = rect.x();
150 y = rect.y();
151 w = rect.width();
152 h = rect.height();
153 }
154 m_nativeQtWindow.callMethod<void>("setGeometry", x, y, w, h);
155 }
156
157 QWindowSystemInterface::handleGeometryChange(window(), rect);
158}
159
161{
163 return;
164
165 if (window()->isTopLevel()) {
166 if (!visible && window() == qGuiApp->focusWindow()) {
167 platformScreen()->topVisibleWindowChanged();
168 } else {
169 const Qt::WindowStates states = window()->windowStates();
170 const Qt::WindowFlags flags = window()->flags();
171 applySystemUi(states, flags);
172 if (states & Qt::WindowFullScreen || flags & Qt::ExpandedClientAreaHint)
173 setGeometry(platformScreen()->geometry());
174 else if (states & Qt::WindowMaximized)
175 setGeometry(platformScreen()->availableGeometry());
177 }
178 }
179
180 m_nativeQtWindow.callMethod<void>("setVisible", visible);
181
182 if (geometry().isEmpty() || screen()->availableGeometry().isEmpty())
183 return;
184
185 QPlatformWindow::setVisible(visible);
186}
187
188void QAndroidPlatformWindow::setWindowState(Qt::WindowStates state)
189{
190 QPlatformWindow::setWindowState(state);
191
192 if (window()->isVisible())
193 applySystemUi(state, window()->flags());
194}
195
196void QAndroidPlatformWindow::setWindowFlags(Qt::WindowFlags flags)
197{
198 QPlatformWindow::setWindowFlags(flags);
199
200 if (window()->isVisible())
201 applySystemUi(window()->windowStates(), flags);
202}
203
204void QAndroidPlatformWindow::setParent(const QPlatformWindow *window)
205{
206 using namespace QtJniTypes;
207
208 if (window) {
209 auto androidWindow = static_cast<const QAndroidPlatformWindow*>(window);
210 if (androidWindow->isEmbeddingContainer())
211 return;
212 // If we were a top level window, remove from screen
213 if (!m_nativeParentQtWindow.isValid())
214 platformScreen()->removeWindow(this);
215
216 const QtWindow parentWindow = androidWindow->nativeWindow();
217 // If this was a child window of another window, the java method takes care of that
218 m_nativeQtWindow.callMethod<void, QtWindow>("setParent", parentWindow.object());
219 m_nativeParentQtWindow = parentWindow;
220 } else if (QtAndroid::isQtApplication()) {
221 m_nativeQtWindow.callMethod<void, QtWindow>("setParent", nullptr);
222 m_nativeParentQtWindow = QJniObject();
223 platformScreen()->addWindow(this);
224 }
225}
226
228{
229 return m_nativeQtWindow.isValid() ? reinterpret_cast<WId>(m_nativeQtWindow.object()) : 0L;
230}
231
233{
234 return static_cast<QAndroidPlatformScreen *>(window()->screen()->handle());
235}
236
238{
239 //shut up warning from default implementation
240}
241
243{
245 return;
246 raise();
247 // For child windows, raise() only updates "Z-order", so focus must be requested explicitly.
248 if (m_nativeParentQtWindow.isValid())
249 QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
250}
251
252namespace {
253// The extended system UI modes Qt can take ownership of. Plain "normal" is not
254// a mode Qt owns, it is the absence of ownership, represented by an empty
255// optional, so window state the app manages itself is never touched by Qt.
256enum class SystemUiMode { Expanded, FullScreen };
257
258std::optional<SystemUiMode> systemUiModeFor(Qt::WindowStates states, Qt::WindowFlags flags)
259{
260 if (states & Qt::WindowFullScreen)
261 return SystemUiMode::FullScreen;
262 if (flags & Qt::ExpandedClientAreaHint)
263 return SystemUiMode::Expanded;
264 return std::nullopt;
265}
266
267// The mode Qt currently owns for the activity window, only ever touched on the
268// gui thread. Empty means Qt owns nothing and leaves the system UI to the app.
269std::optional<SystemUiMode> g_ownedSystemUiMode;
270
271void applySystemUiMode(std::optional<SystemUiMode> mode)
272{
273 g_ownedSystemUiMode = mode;
274
275 auto iface = qGuiApp->nativeInterface<QNativeInterface::QAndroidApplication>();
276 iface->runOnAndroidMainThread([mode, iface]() {
277 using namespace QtJniTypes;
278 auto activity = iface->context().object<Activity>();
279 if (mode == SystemUiMode::FullScreen)
280 QtWindowInsetsController::callStaticMethod("showFullScreen", activity);
281 else if (mode == SystemUiMode::Expanded)
282 QtWindowInsetsController::callStaticMethod("showExpanded", activity);
283 else
284 QtWindowInsetsController::callStaticMethod("showNormal", activity);
285 });
286}
287} // unnamed namespace
288
289void QAndroidPlatformWindow::applySystemUi(Qt::WindowStates states, Qt::WindowFlags flags)
290{
291 const bool isNonRegularWindow = flags & (Qt::Popup | Qt::Dialog | Qt::Sheet) & ~Qt::Window;
292 if (isNonRegularWindow)
293 return;
294
295 const std::optional<SystemUiMode> mode = systemUiModeFor(states, flags);
296 if (mode != g_ownedSystemUiMode)
297 applySystemUiMode(mode);
298}
299
300void QAndroidPlatformWindow::applySystemUiNative(JNIEnv *env, jobject object)
301{
302 Q_UNUSED(env)
303 Q_UNUSED(object)
304
305 if (!qGuiApp)
306 return;
307
308 // Some lifecycle events reset window state on the OS side. Re-assert the
309 // mode Qt owns, if any. When Qt owns nothing the app keeps its own state.
310 QMetaObject::invokeMethod(qGuiApp, [] {
311 if (g_ownedSystemUiMode)
312 applySystemUiMode(g_ownedSystemUiMode);
313 });
314}
315
317{
318 m_nativeQtWindow.callMethod<void>("updateFocusedEditText");
319}
320
322{
323 return qApp->applicationState() > Qt::ApplicationHidden
324 && window()->isVisible()
325 && !window()->geometry().isEmpty();
326}
327
329{
330 QRegion region;
331 if (isExposed())
332 region = QRect(QPoint(), geometry().size());
333
334 QWindowSystemInterface::handleExposeEvent(window(), region);
335 QWindowSystemInterface::flushWindowSystemEvents();
336}
337
339{
340 const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
341 const bool isOpaque = !format().hasAlpha() && qFuzzyCompare(window()->opacity(), qreal(1.0));
342
343 m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, 32, isOpaque,
344 m_surfaceContainerType);
346}
347
349{
351 m_nativeQtWindow.callMethod<void>("destroySurface");
353 }
354}
355
356void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
357{
359 const bool surfaceIsValid = surface.isValid();
360 qCDebug(lcQpaWindow) << "onSurfaceChanged(): valid Surface received" << surfaceIsValid;
361 m_androidSurfaceObject = surface;
362 if (surfaceIsValid) {
363 // wait until we have a valid surface to draw into
364 m_surfaceWaitCondition.wakeOne();
365 } else {
367 }
368
370
372}
373
375{
376 QRect availableGeometry = screen()->availableGeometry();
377 if (!geometry().isNull() && !availableGeometry.isNull()) {
378 QWindowSystemInterface::handleExposeEvent(window(),
379 QRegion(QRect(QPoint(), geometry().size())));
380 }
381}
382
384{
385 QWindow *modalWindow = QGuiApplication::modalWindow();
386 return modalWindow && modalWindow != window();
387}
388
390{
391 // Returns true if the window is a wrapper for a foreign window solely to allow embedding Qt
392 // into a native Android app, in which case we should not try to control it more than we "need" to
393 return !QtAndroid::isQtApplication() && window()->isTopLevel();
394}
395
396void QAndroidPlatformWindow::setSurface(JNIEnv *env, jobject object, jint windowId,
397 QtJniTypes::Surface surface)
398{
399 Q_UNUSED(env)
400 Q_UNUSED(object)
401
402 if (!qGuiApp)
403 return;
404
405 const QList<QWindow*> windows = qGuiApp->allWindows();
406 for (QWindow * window : windows) {
407 if (!window->handle())
408 continue;
409 QAndroidPlatformWindow *platformWindow =
410 static_cast<QAndroidPlatformWindow *>(window->handle());
411 const auto guard = platformWindow->destructionGuard();
412 if (!platformWindow->m_androidSurfaceCreated)
413 continue;
414 if (platformWindow->nativeViewId() == windowId)
415 platformWindow->onSurfaceChanged(surface);
416 }
417}
418
419void QAndroidPlatformWindow::windowFocusChanged(JNIEnv *env, jobject object,
420 jboolean focus, jint windowId)
421{
422 Q_UNUSED(env)
423 Q_UNUSED(object)
424 QWindow* window = QtAndroid::windowFromId(windowId);
425 if (!window) {
426 qCWarning(lcQpaWindow,
427 "windowFocusChanged event received for non-existing window %d", windowId);
428 return;
429 }
430
431 if (focus) {
432 QWindowSystemInterface::handleFocusWindowChanged(window);
433 } else if (!focus && window == qGuiApp->focusWindow()) {
434 // Clear focus if current window has lost focus
435 QWindowSystemInterface::handleFocusWindowChanged(nullptr);
436 }
437}
438
439void QAndroidPlatformWindow::safeAreaMarginsChanged(JNIEnv *env, jobject object,
440 QtJniTypes::Insets insets, jint id)
441{
442 Q_UNUSED(env)
443 Q_UNUSED(object)
444
445 if (!qGuiApp)
446 return;
447
448 if (!insets.isValid())
449 return;
450
451 QAndroidPlatformWindow *pWindow = nullptr;
452 for (QWindow *window : qGuiApp->allWindows()) {
453 if (!window->handle())
454 continue;
455 QAndroidPlatformWindow *pw = static_cast<QAndroidPlatformWindow *>(window->handle());
456 if (pw->nativeViewId() == id) {
457 pWindow = pw;
458 break;
459 }
460 }
461
462 if (!pWindow)
463 return;
464
465 QMargins safeMargins = QMargins(
466 insets.getField<int>("left"),
467 insets.getField<int>("top"),
468 insets.getField<int>("right"),
469 insets.getField<int>("bottom"));
470
471 if (safeMargins != pWindow->safeAreaMargins()) {
472 pWindow->setSafeAreaMargins(safeMargins);
473 QWindowSystemInterface::handleSafeAreaMarginsChanged(pWindow->window());
474 }
475}
476
477static void updateWindows(JNIEnv *env, jobject object)
478{
479 Q_UNUSED(env)
480 Q_UNUSED(object)
481
482 if (QGuiApplication::instance() != nullptr) {
483 const auto tlw = QGuiApplication::topLevelWindows();
484 for (QWindow *w : tlw) {
485
486 // Skip non-platform windows, e.g., offscreen windows.
487 if (!w->handle())
488 continue;
489
490 const QRect availableGeometry = w->screen()->availableGeometry();
491 const QRect geometry = w->geometry();
492 const bool isPositiveGeometry = (geometry.width() > 0 && geometry.height() > 0);
493 const bool isPositiveAvailableGeometry =
494 (availableGeometry.width() > 0 && availableGeometry.height() > 0);
495
496 if (isPositiveGeometry && isPositiveAvailableGeometry) {
497 const QRegion region = QRegion(QRect(QPoint(), w->geometry().size()));
498 QWindowSystemInterface::handleExposeEvent(w, region);
499 }
500 }
501 }
502}
503Q_DECLARE_JNI_NATIVE_METHOD(updateWindows)
504
505/*
506 Due to calls originating from Android, it is possible for native methods to
507 try to manipulate any given instance of QAndroidPlatformWindow when it is
508 already being destroyed. So we use this to guard against that. It is called
509 in the destructor, and should also be called in any function registered to
510 be called from java that may touch an instance of QAndroidPlatformWindow.
511 */
512QMutexLocker<QMutex> QAndroidPlatformWindow::destructionGuard()
513{
514 return QMutexLocker(&m_destructionMutex);
515}
516
517Q_CONSTINIT static QBasicAtomicInt g_surfacesCounter = Q_BASIC_ATOMIC_INITIALIZER(0);
518
520{
521 return g_surfacesCounter.loadRelaxed();
522}
523
525{
526 g_surfacesCounter.fetchAndAddRelaxed(1);
527}
528
530{
531 int cur = g_surfacesCounter.loadRelaxed();
532 while (true) {
533 if (cur == 0)
534 return;
535
536 if (g_surfacesCounter.testAndSetRelaxed(cur, cur - 1))
537 return;
538 }
539}
540
541bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env)
542{
543 if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtWindow>::className(),
544 {
545 Q_JNI_NATIVE_METHOD(updateWindows),
546 Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow),
547 Q_JNI_NATIVE_SCOPED_METHOD(windowFocusChanged, QAndroidPlatformWindow),
548 Q_JNI_NATIVE_SCOPED_METHOD(safeAreaMarginsChanged, QAndroidPlatformWindow)
549 })) {
550 qCCritical(lcQpaWindow) << "RegisterNatives failed for"
551 << QtJniTypes::Traits<QtJniTypes::QtWindow>::className();
552 return false;
553 }
554
555 if (!env.registerNativeMethods(
556 QtJniTypes::Traits<QtJniTypes::QtWindowInsetsController>::className(),
557 {
558 Q_JNI_NATIVE_SCOPED_METHOD(applySystemUiNative, QAndroidPlatformWindow)
559 })) {
560 qCCritical(lcQpaWindow) << "RegisterNatives failed for"
561 << QtJniTypes::Traits<QtJniTypes::QtWindowInsetsController>::className();
562 return false;
563 }
564 return true;
565}
566
567QT_END_NAMESPACE
void propagateSizeHints() override
Reimplement to propagate the size hints of the QWindow.
virtual void applicationStateChanged(Qt::ApplicationState)
void setSafeAreaMargins(const QMargins safeMargins)
void setWindowFlags(Qt::WindowFlags flags) override
Requests setting the window flags of this surface to flags.
void setWindowState(Qt::WindowStates state) override
Requests setting the window state of this surface to type.
void onSurfaceChanged(QtJniTypes::Surface surface)
bool isExposed() const override
Returns if this window is exposed in the windowing system.
SurfaceContainer m_surfaceContainerType
QAndroidPlatformScreen * platformScreen() const
void requestActivateWindow() override
Reimplement to let Qt be able to request activation/focus for a window.
void applySystemUi(Qt::WindowStates states, Qt::WindowFlags flags)
void initialize() override
Called as part of QWindow::create(), after constructing the window.
void setVisible(bool visible) override
Reimplemented in subclasses to show the surface if visible is true, and hide it if visible is false.
void setParent(const QPlatformWindow *window) override
This function is called to enable native child window in QPA.
void lower() override
Reimplement to be able to let Qt lower windows to the bottom of the desktop.
WId winId() const override
Reimplement in subclasses to return a handle to the native window.
void raise() override
Reimplement to be able to let Qt raise windows to the top of the desktop.
void setGeometry(const QRect &rect) override
This function is called by Qt whenever a window is moved or resized using the QWindow API.
QMargins safeAreaMargins() const override
The safe area margins of a window represent the area that is safe to place content within,...
QObject * parent
Definition qobject.h:74
AndroidBackendRegister * backendRegister()
QWindow * windowFromId(int windowId)
bool isQtApplication()
static void updateWindows(JNIEnv *env, jobject object)
#define qApp
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define qGuiApp