Android: Make QtLayout per window instead of per screen

Each QAndroidPlatformWindow has its own QtLayout,
instead of one for the whole app/screen. This paves the
way for addition of child windows.

Task-number: QTBUG-116187
Change-Id: I36c68cea1a5f27ded3696bcfc2fbc04d9a8ce79e
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Tinja Paavoseppä 2023-09-05 09:01:46 +03:00
parent 0bfec6cd11
commit 0a92d881bb
15 changed files with 385 additions and 382 deletions

View File

@ -29,6 +29,7 @@ set(java_sources
src/org/qtproject/qt/android/QtClipboardManager.java
src/org/qtproject/qt/android/QtDisplayManager.java
src/org/qtproject/qt/android/UsedFromNativeCode.java
src/org/qtproject/qt/android/QtWindow.java
)
qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android

View File

@ -36,9 +36,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
private View m_view = null;
private final AccessibilityManager m_manager;
private final QtActivityDelegate m_activityDelegate;
private final Activity m_activity;
private final ViewGroup m_layout;
private final QtLayout m_layout;
// The accessible object that currently has the "accessibility focus"
// usually indicated by a yellow rectangle on screen.
@ -62,13 +60,11 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
}
}
public QtAccessibilityDelegate(Activity activity, ViewGroup layout, QtActivityDelegate activityDelegate)
public QtAccessibilityDelegate(QtLayout layout)
{
m_activity = activity;
m_layout = layout;
m_activityDelegate = activityDelegate;
m_manager = (AccessibilityManager) m_activity.getSystemService(Context.ACCESSIBILITY_SERVICE);
m_manager = (AccessibilityManager) m_layout.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (m_manager != null) {
AccessibilityManagerListener accServiceListener = new AccessibilityManagerListener();
if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
@ -89,7 +85,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
try {
View view = m_view;
if (view == null) {
view = new View(m_activity);
view = new View(m_layout.getContext());
view.setId(View.NO_ID);
}
@ -105,7 +101,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
// if all is fine, add it to the layout
if (m_view == null) {
//m_layout.addAccessibilityView(view);
m_layout.addView(view, m_activityDelegate.getSurfaceCount(),
m_layout.addView(view, m_layout.getChildCount(),
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
m_view = view;
@ -289,7 +285,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
return null;
}
if (m_activityDelegate.getSurfaceCount() == 0)
if (m_layout.getChildCount() == 0)
return null;
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
@ -356,7 +352,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
// Spit out the entire hierarchy for debugging purposes
// dumpNodes(-1);
if (m_activityDelegate.getSurfaceCount() != 0) {
if (m_layout.getChildCount() == 0) {
int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
for (int id : ids)
result.addChild(m_view, id);
@ -388,7 +384,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
node.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
node.setPackageName(m_view.getContext().getPackageName());
if (m_activityDelegate.getSurfaceCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
return node;
}
@ -439,7 +435,7 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate
@Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId)
{
if (virtualViewId == View.NO_ID || m_activityDelegate.getSurfaceCount() == 0) {
if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
return getNodeForView();
}
return getNodeForVirtualViewId(virtualViewId);

View File

@ -11,6 +11,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.os.Build;
import android.util.DisplayMetrics;
@ -36,9 +37,8 @@ class QtActivityDelegate
{
private Activity m_activity;
private HashMap<Integer, QtSurface> m_surfaces = null;
private HashMap<Integer, View> m_nativeViews = null;
private QtLayout m_layout = null;
private HashMap<Integer, QtWindow> m_topLevelWindows;
private ImageView m_splashScreen = null;
private boolean m_splashScreenSticky = false;
@ -48,6 +48,7 @@ class QtActivityDelegate
private QtDisplayManager m_displayManager = null;
private QtInputDelegate m_inputDelegate = null;
private boolean m_membersInitialized = false;
QtActivityDelegate(Activity activity)
{
@ -55,6 +56,7 @@ class QtActivityDelegate
QtNative.setActivity(m_activity);
setActionBarVisibility(false);
setActivityBackgroundDrawable();
}
QtDisplayManager displayManager() {
@ -116,7 +118,7 @@ class QtActivityDelegate
public void startNativeApplication(String appParams, String mainLib)
{
if (m_surfaces != null)
if (m_membersInitialized)
return;
initMembers();
@ -134,6 +136,8 @@ class QtActivityDelegate
private void initMembers()
{
m_layout = new QtLayout(m_activity);
m_membersInitialized = true;
m_topLevelWindows = new HashMap<Integer, QtWindow>();
m_displayManager = new QtDisplayManager(m_activity);
m_displayManager.registerDisplayListener();
@ -173,8 +177,6 @@ class QtActivityDelegate
e.printStackTrace();
}
m_surfaces = new HashMap<>();
m_nativeViews = new HashMap<>();
m_activity.registerForContextMenu(m_layout);
m_activity.setContentView(m_layout,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
@ -293,9 +295,7 @@ class QtActivityDelegate
@UsedFromNativeCode
public void initializeAccessibility()
{
final QtActivityDelegate currentDelegate = this;
QtNative.runAction(() -> m_accessibilityDelegate = new QtAccessibilityDelegate(m_activity,
m_layout, currentDelegate));
QtNative.runAction(() -> m_accessibilityDelegate = new QtAccessibilityDelegate(m_layout));
}
void handleUiModeChange(int uiMode)
@ -377,135 +377,52 @@ class QtActivityDelegate
}
@UsedFromNativeCode
public void insertNativeView(int id, View view, int x, int y, int w, int h) {
QtNative.runAction(() -> {
if (m_dummyView != null) {
m_layout.removeView(m_dummyView);
m_dummyView = null;
}
if (m_nativeViews.containsKey(id))
m_layout.removeView(m_nativeViews.remove(id));
if (w < 0 || h < 0) {
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
} else {
view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
}
view.setId(id);
m_layout.addView(view);
m_nativeViews.put(id, view);
});
}
@UsedFromNativeCode
public void createSurface(int id, boolean onTop, int x, int y, int w, int h, int imageDepth) {
QtNative.runAction(() -> {
if (m_surfaces.size() == 0) {
TypedValue attr = new TypedValue();
m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground, attr, true);
if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT && attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
m_activity.getWindow().setBackgroundDrawable(new ColorDrawable(attr.data));
} else {
m_activity.getWindow().setBackgroundDrawable(m_activity.getResources().getDrawable(attr.resourceId, m_activity.getTheme()));
}
public void addTopLevelWindow(final QtWindow window)
{
QtNative.runAction(()-> {
if (m_topLevelWindows.size() == 0) {
if (m_dummyView != null) {
m_layout.removeView(m_dummyView);
m_dummyView = null;
}
}
if (m_surfaces.containsKey(id))
m_layout.removeView(m_surfaces.remove(id));
window.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
QtSurface surface = new QtSurface(m_activity, id, onTop, imageDepth);
if (w < 0 || h < 0) {
surface.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
} else {
surface.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
}
// Native views are always inserted in the end of the stack (i.e., on top).
// All other views are stacked based on the order they are created.
final int surfaceCount = getSurfaceCount();
m_layout.addView(surface, surfaceCount);
m_surfaces.put(id, surface);
m_layout.addView(window, m_topLevelWindows.size());
m_topLevelWindows.put(window.getId(), window);
if (!m_splashScreenSticky)
hideSplashScreen();
});
}
@UsedFromNativeCode
public void setSurfaceGeometry(int id, int x, int y, int w, int h) {
QtNative.runAction(() -> {
if (m_surfaces.containsKey(id)) {
QtSurface surface = m_surfaces.get(id);
if (surface != null)
surface.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
else
Log.e(QtNative.QtTAG, "setSurfaceGeometry(): surface is null!");
} else if (m_nativeViews.containsKey(id)) {
View view = m_nativeViews.get(id);
if (view != null)
view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
else
Log.e(QtNative.QtTAG, "setSurfaceGeometry(): view is null!");
} else {
Log.e(QtNative.QtTAG, "Surface " + id + " not found!");
public void removeTopLevelWindow(final int id)
{
QtNative.runAction(()-> {
if (m_topLevelWindows.containsKey(id)) {
QtWindow window = m_topLevelWindows.remove(id);
if (m_topLevelWindows.isEmpty()) {
// Keep last frame in stack until it is replaced to get correct
// shutdown transition
m_dummyView = window;
} else {
m_layout.removeView(window);
}
}
});
}
@UsedFromNativeCode
public void destroySurface(int id) {
QtNative.runAction(() -> {
View view = null;
if (m_surfaces.containsKey(id)) {
view = m_surfaces.remove(id);
} else if (m_nativeViews.containsKey(id)) {
view = m_nativeViews.remove(id);
} else {
Log.e(QtNative.QtTAG, "Surface " + id + " not found!");
}
if (view == null)
return;
// Keep last frame in stack until it is replaced to get correct
// shutdown transition
if (m_surfaces.size() == 0 && m_nativeViews.size() == 0) {
m_dummyView = view;
} else {
m_layout.removeView(view);
}
});
}
public int getSurfaceCount()
{
return m_surfaces.size();
}
@UsedFromNativeCode
public void bringChildToFront(int id)
public void bringChildToFront(final int id)
{
QtNative.runAction(() -> {
View view = m_surfaces.get(id);
if (view != null) {
final int surfaceCount = getSurfaceCount();
if (surfaceCount > 0)
m_layout.moveChild(view, surfaceCount - 1);
return;
QtWindow window = m_topLevelWindows.get(id);
if (window != null) {
m_layout.moveChild(window, m_topLevelWindows.size() - 1);
}
view = m_nativeViews.get(id);
if (view != null)
m_layout.moveChild(view, -1);
});
}
@ -513,17 +430,26 @@ class QtActivityDelegate
public void bringChildToBack(int id)
{
QtNative.runAction(() -> {
View view = m_surfaces.get(id);
if (view != null) {
m_layout.moveChild(view, 0);
return;
}
view = m_nativeViews.get(id);
if (view != null) {
final int index = getSurfaceCount();
m_layout.moveChild(view, index);
}
QtWindow window = m_topLevelWindows.get(id);
if (window != null)
m_layout.moveChild(window, 0);
});
}
private void setActivityBackgroundDrawable()
{
TypedValue attr = new TypedValue();
m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground,
attr, true);
Drawable backgroundDrawable;
if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
backgroundDrawable = new ColorDrawable(attr.data);
} else {
backgroundDrawable = m_activity.getResources().
getDrawable(attr.resourceId, m_activity.getTheme());
}
m_activity.getWindow().setBackgroundDrawable(backgroundDrawable);
}
}

View File

@ -9,6 +9,7 @@ import android.content.Context;
import android.graphics.PixelFormat;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@ -16,24 +17,33 @@ import android.view.SurfaceView;
class QtSurface extends SurfaceView implements SurfaceHolder.Callback
{
private final GestureDetector m_gestureDetector;
private Object m_accessibilityDelegate = null;
private SurfaceChangedCallback m_surfaceCallback;
private final int m_windowId;
public QtSurface(Context context, int id, boolean onTop, int imageDepth)
interface SurfaceChangedCallback {
void onSurfaceChanged(Surface surface);
}
public QtSurface(Context context, SurfaceChangedCallback surfaceCallback, int id, boolean onTop,
int imageDepth)
{
super(context);
setFocusable(false);
setFocusableInTouchMode(false);
setZOrderMediaOverlay(onTop);
m_surfaceCallback = surfaceCallback;
getHolder().addCallback(this);
if (imageDepth == 16)
getHolder().setFormat(PixelFormat.RGB_565);
else
getHolder().setFormat(PixelFormat.RGBA_8888);
setId(id);
m_windowId = id;
m_gestureDetector =
new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent event) {
QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY());
QtInputDelegate.longPress(m_windowId, (int) event.getX(), (int) event.getY());
}
});
m_gestureDetector.setIsLongpressEnabled(true);
@ -49,14 +59,15 @@ class QtSurface extends SurfaceView implements SurfaceHolder.Callback
{
if (width < 1 || height < 1)
return;
QtNative.setSurface(getId(), holder.getSurface());
if (m_surfaceCallback != null)
m_surfaceCallback.onSurfaceChanged(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
QtNative.setSurface(getId(), null);
if (m_surfaceCallback != null)
m_surfaceCallback.onSurfaceChanged(null);
}
@Override
@ -67,7 +78,7 @@ class QtSurface extends SurfaceView implements SurfaceHolder.Callback
// In case when Surface is moved, we should also add this move to event position
event.setLocation(event.getX() + getX(), event.getY() + getY());
QtInputDelegate.sendTouchEvent(event, getId());
QtInputDelegate.sendTouchEvent(event, m_windowId);
m_gestureDetector.onTouchEvent(event);
return true;
}
@ -75,13 +86,13 @@ class QtSurface extends SurfaceView implements SurfaceHolder.Callback
@Override
public boolean onTrackballEvent(MotionEvent event)
{
QtInputDelegate.sendTrackballEvent(event, getId());
QtInputDelegate.sendTrackballEvent(event, m_windowId);
return true;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
return QtInputDelegate.sendGenericMotionEvent(event, getId());
return QtInputDelegate.sendGenericMotionEvent(event, m_windowId);
}
}

View File

@ -0,0 +1,114 @@
// Copyright (C) 2023 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
package org.qtproject.qt.android;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import java.util.HashMap;
public class QtWindow extends QtLayout implements QtSurface.SurfaceChangedCallback {
private final static String TAG = "QtWindow";
private QtSurface m_surface;
private View m_nativeView;
private Handler m_androidHandler;
private static native void setSurface(int windowId, Surface surface);
public QtWindow(Context context)
{
super(context);
setId(View.generateViewId());
}
@Override
public void onSurfaceChanged(Surface surface)
{
setSurface(getId(), surface);
}
public void createSurface(final boolean onTop,
final int x, final int y, final int w, final int h,
final int imageDepth)
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_surface != null)
removeView(m_surface);
QtSurface surface = new QtSurface(getContext(),
QtWindow.this, QtWindow.this.getId(),
onTop, imageDepth);
surface.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
addView(surface, 0);
m_surface = surface;
}
});
}
public void destroySurface()
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_surface != null) {
removeView(m_surface);
m_surface = null;
}
}
});
}
public void setSurfaceGeometry(final int x, final int y, final int w, final int h)
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
QtLayout.LayoutParams lp = new QtLayout.LayoutParams(w, h, x, y);
if (m_surface != null)
m_surface.setLayoutParams(lp);
else if (m_nativeView != null)
m_nativeView.setLayoutParams(lp);
}
});
}
public void setNativeView(final View view,
final int x, final int y, final int w, final int h)
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_nativeView != null)
removeView(m_nativeView);
m_nativeView = view;
m_nativeView.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
addView(m_nativeView);
}
});
}
public void removeNativeView()
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_nativeView != null) {
removeView(m_nativeView);
m_nativeView = null;
}
}
});
}
}

View File

@ -70,10 +70,6 @@ static void *m_mainLibraryHnd = nullptr;
static QList<QByteArray> m_applicationParams;
static sem_t m_exitSemaphore, m_terminateSemaphore;
QHash<int, QAndroidPlatformWindow *> m_surfaces;
Q_CONSTINIT static QBasicMutex m_surfacesMutex;
static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr;
@ -319,56 +315,6 @@ namespace QtAndroid
return manufacturer + u' ' + model;
}
jint generateViewId()
{
return QJniObject::callStaticMethod<jint>("android/view/View", "generateViewId", "()I");
}
int createSurface(QAndroidPlatformWindow *window, const QRect &geometry, bool onTop, int imageDepth)
{
QJniEnvironment env;
if (!env.jniEnv())
return -1;
m_surfacesMutex.lock();
jint surfaceId = generateViewId();
m_surfaces[surfaceId] = window;
m_surfacesMutex.unlock();
jint x = 0, y = 0, w = -1, h = -1;
if (!geometry.isNull()) {
x = geometry.x();
y = geometry.y();
w = std::max(geometry.width(), 1);
h = std::max(geometry.height(), 1);
}
qtActivityDelegate().callMethod<void>("createSurface", surfaceId, jboolean(onTop),
x, y, w, h, imageDepth);
return surfaceId;
}
int insertNativeView(QtJniTypes::View view, const QRect &geometry)
{
m_surfacesMutex.lock();
jint surfaceId = generateViewId();
m_surfaces[surfaceId] = nullptr; // dummy
m_surfacesMutex.unlock();
jint x = 0, y = 0, w = -1, h = -1;
if (!geometry.isNull())
geometry.getRect(&x, &y, &w, &h);
qtActivityDelegate().callMethod<void>("insertNativeView",
surfaceId,
view,
x,
y,
qMax(w, 1),
qMax(h, 1));
return surfaceId;
}
void setViewVisibility(jobject view, bool visible)
{
QJniObject::callStaticMethod<void>(m_applicationClass,
@ -378,56 +324,6 @@ namespace QtAndroid
visible);
}
void setSurfaceGeometry(int surfaceId, const QRect &geometry)
{
if (surfaceId == -1)
return;
QJniEnvironment env;
if (!env.jniEnv())
return;
jint x = 0, y = 0, w = -1, h = -1;
if (!geometry.isNull()) {
x = geometry.x();
y = geometry.y();
w = geometry.width();
h = geometry.height();
}
qtActivityDelegate().callMethod<void>("setSurfaceGeometry", surfaceId, x, y, w, h);
}
void destroySurface(int surfaceId)
{
if (surfaceId == -1)
return;
{
QMutexLocker lock(&m_surfacesMutex);
const auto &it = m_surfaces.find(surfaceId);
if (it != m_surfaces.end())
m_surfaces.erase(it);
}
qtActivityDelegate().callMethod<void>("destroySurface", surfaceId);
}
void bringChildToFront(int surfaceId)
{
if (surfaceId == -1)
return;
qtActivityDelegate().callMethod<void>("bringChildToFront", surfaceId);
}
void bringChildToBack(int surfaceId)
{
if (surfaceId == -1)
return;
qtActivityDelegate().callMethod<void>("bringChildToBack", surfaceId);
}
bool blockEventLoopsWhenSuspended()
{
static bool block = qEnvironmentVariableIntValue("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED");
@ -595,21 +491,6 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
sem_post(&m_exitSemaphore);
}
static void setSurface(JNIEnv *env, jobject thiz, jint id, jobject jSurface)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QMutexLocker lock(&m_surfacesMutex);
const auto &it = m_surfaces.find(id);
if (it == m_surfaces.end())
return;
auto surfaceClient = it.value();
if (surfaceClient)
surfaceClient->onSurfaceChanged(jSurface);
}
static void setDisplayMetrics(JNIEnv * /*env*/, jclass /*clazz*/, jint screenWidthPixels,
jint screenHeightPixels, jint availableLeftPixels,
jint availableTopPixels, jint availableWidthPixels,
@ -802,7 +683,6 @@ static JNINativeMethod methods[] = {
{ "quitQtCoreApplication", "()V", (void *)quitQtCoreApplication },
{ "terminateQt", "()V", (void *)terminateQt },
{ "waitForServiceSetup", "()V", (void *)waitForServiceSetup },
{ "setSurface", "(ILjava/lang/Object;)V", (void *)setSurface },
{ "updateWindow", "()V", (void *)updateWindow },
{ "updateApplicationState", "(I)V", (void *)updateApplicationState },
{ "onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult },
@ -948,7 +828,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
|| !QtAndroidMenu::registerNatives(env)
|| !QtAndroidAccessibility::registerNatives(env)
|| !QtAndroidDialogHelpers::registerNatives(env)
|| !QAndroidPlatformClipboard::registerNatives(env)) {
|| !QAndroidPlatformClipboard::registerNatives(env)
|| !QAndroidPlatformWindow::registerNatives(env)) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
}

View File

@ -28,7 +28,6 @@ class QBasicMutex;
Q_DECLARE_JNI_CLASS(QtActivityDelegate, "org/qtproject/qt/android/QtActivityDelegate")
Q_DECLARE_JNI_CLASS(QtInputDelegate, "org/qtproject/qt/android/QtInputDelegate")
Q_DECLARE_JNI_CLASS(View, "android/view/View");
namespace QtAndroid
{
@ -36,14 +35,7 @@ namespace QtAndroid
QAndroidPlatformIntegration *androidPlatformIntegration();
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration);
void setQtThread(QThread *thread);
int createSurface(QAndroidPlatformWindow *window, const QRect &geometry, bool onTop, int imageDepth);
int insertNativeView(QtJniTypes::View view, const QRect &geometry);
void setViewVisibility(jobject view, bool visible);
void setSurfaceGeometry(int surfaceId, const QRect &geometry);
void destroySurface(int surfaceId);
void bringChildToFront(int surfaceId);
void bringChildToBack(int surfaceId);
QWindow *topLevelWindowAt(const QPoint &globalPos);
int availableWidthPixels();

View File

@ -6,12 +6,12 @@
#include <QtCore/qvariant.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/qjnitypes.h>
QT_BEGIN_NAMESPACE
QAndroidPlatformForeignWindow::QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle)
: QAndroidPlatformWindow(window),
m_surfaceId(-1)
: QAndroidPlatformWindow(window), m_view(nullptr), m_nativeViewInserted(false)
{
m_view = reinterpret_cast<jobject>(nativeHandle);
if (m_view.isValid())
@ -22,34 +22,17 @@ QAndroidPlatformForeignWindow::~QAndroidPlatformForeignWindow()
{
if (m_view.isValid())
QtAndroid::setViewVisibility(m_view.object(), false);
if (m_surfaceId != -1)
QtAndroid::destroySurface(m_surfaceId);
}
void QAndroidPlatformForeignWindow::lower()
{
if (m_surfaceId == -1)
return;
m_nativeQtWindow.callMethod<void>("removeNativeView");
QAndroidPlatformWindow::lower();
QtAndroid::bringChildToBack(m_surfaceId);
}
void QAndroidPlatformForeignWindow::raise()
{
if (m_surfaceId == -1)
return;
QAndroidPlatformWindow::raise();
QtAndroid::bringChildToFront(m_surfaceId);
}
void QAndroidPlatformForeignWindow::setGeometry(const QRect &rect)
{
QAndroidPlatformWindow::setGeometry(rect);
if (m_surfaceId != -1)
QtAndroid::setSurfaceGeometry(m_surfaceId, rect);
if (m_nativeViewInserted)
setSurfaceGeometry(rect);
}
void QAndroidPlatformForeignWindow::setVisible(bool visible)
@ -60,22 +43,21 @@ void QAndroidPlatformForeignWindow::setVisible(bool visible)
QtAndroid::setViewVisibility(m_view.object(), visible);
QAndroidPlatformWindow::setVisible(visible);
if (!visible && m_surfaceId != -1) {
QtAndroid::destroySurface(m_surfaceId);
m_surfaceId = -1;
} else if (m_surfaceId == -1) {
m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
if (!visible && m_nativeViewInserted) {
m_nativeQtWindow.callMethod<void>("removeNativeView");
m_nativeViewInserted = false;
} else if (!m_nativeViewInserted) {
addViewToWindow();
}
}
void QAndroidPlatformForeignWindow::applicationStateChanged(Qt::ApplicationState state)
{
if (state <= Qt::ApplicationHidden
&& m_surfaceId != -1) {
QtAndroid::destroySurface(m_surfaceId);
m_surfaceId = -1;
} else if (m_view.isValid() && m_surfaceId == -1){
m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
if (state <= Qt::ApplicationHidden && m_nativeViewInserted) {
m_nativeQtWindow.callMethod<void>("removeNativeView");
m_nativeViewInserted = false;
} else if (m_view.isValid() && !m_nativeViewInserted){
addViewToWindow();
}
QAndroidPlatformWindow::applicationStateChanged(state);
@ -86,4 +68,14 @@ void QAndroidPlatformForeignWindow::setParent(const QPlatformWindow *window)
Q_UNUSED(window);
}
void QAndroidPlatformForeignWindow::addViewToWindow()
{
jint x = 0, y = 0, w = -1, h = -1;
if (!geometry().isNull())
geometry().getRect(&x, &y, &w, &h);
m_nativeQtWindow.callMethod<void>("setNativeView", m_view, x, y, qMax(w, 1), qMax(h, 1));
m_nativeViewInserted = true;
}
QT_END_NAMESPACE

View File

@ -10,13 +10,13 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_JNI_CLASS(View, "android/view/View")
class QAndroidPlatformForeignWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle);
~QAndroidPlatformForeignWindow();
void lower() override;
void raise() override;
void setGeometry(const QRect &rect) override;
void setVisible(bool visible) override;
void applicationStateChanged(Qt::ApplicationState state) override;
@ -24,8 +24,10 @@ public:
bool isForeignWindow() const override { return true; }
private:
int m_surfaceId;
QJniObject m_view;
void addViewToWindow();
QtJniTypes::View m_view;
bool m_nativeViewInserted;
};
QT_END_NAMESPACE

View File

@ -58,12 +58,14 @@ void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
{
if (QAndroidEventDispatcherStopper::stopped() || QGuiApplication::applicationState() == Qt::ApplicationSuspended)
if (QAndroidEventDispatcherStopper::stopped() ||
QGuiApplication::applicationState() == Qt::ApplicationSuspended || !window()->isTopLevel()) {
return m_eglSurface;
}
QMutexLocker lock(&m_surfaceMutex);
if (m_nativeSurfaceId == -1) {
if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return m_eglSurface;
@ -83,7 +85,7 @@ EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
bool QAndroidPlatformOpenGLWindow::checkNativeSurface(EGLConfig config)
{
QMutexLocker lock(&m_surfaceMutex);
if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return false; // makeCurrent is NOT needed.
createEgl(config);

View File

@ -132,16 +132,16 @@ QAndroidPlatformScreen::~QAndroidPlatformScreen()
{
}
QWindow *QAndroidPlatformScreen::topWindow() const
QWindow *QAndroidPlatformScreen::topVisibleWindow() const
{
for (QAndroidPlatformWindow *w : m_windowStack) {
if (w->window()->type() == Qt::Window ||
w->window()->type() == Qt::Popup ||
w->window()->type() == Qt::Dialog) {
Qt::WindowType type = w->window()->type();
if (w->window()->isVisible() &&
(type == Qt::Window || type == Qt::Popup || type == Qt::Dialog)) {
return w->window();
}
}
return 0;
return nullptr;
}
QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const
@ -162,37 +162,34 @@ void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window)
return;
m_windowStack.prepend(window);
QtAndroid::qtActivityDelegate().callMethod<void>("addTopLevelWindow", window->nativeWindow());
QWindow *w = topWindow();
QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
topWindowChanged(w);
if (window->window()->isVisible())
topVisibleWindowChanged();
}
void QAndroidPlatformScreen::removeWindow(QAndroidPlatformWindow *window)
{
if (window->parent() && window->isRaster())
return;
m_windowStack.removeOne(window);
if (m_windowStack.contains(window))
qWarning() << "Failed to remove window";
QWindow *w = topWindow();
QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
topWindowChanged(w);
QtAndroid::qtActivityDelegate().callMethod<void>("removeTopLevelWindow", window->nativeViewId());
topVisibleWindowChanged();
}
void QAndroidPlatformScreen::raise(QAndroidPlatformWindow *window)
{
int index = m_windowStack.indexOf(window);
if (index <= 0)
if (index < 0)
return;
m_windowStack.move(index, 0);
QWindow *w = topWindow();
QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
topWindowChanged(w);
if (index > 0) {
m_windowStack.move(index, 0);
QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToFront", window->nativeViewId());
}
topVisibleWindowChanged();
}
void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window)
@ -201,10 +198,9 @@ void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window)
if (index == -1 || index == (m_windowStack.size() - 1))
return;
m_windowStack.move(index, m_windowStack.size() - 1);
QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToBack", window->nativeViewId());
QWindow *w = topWindow();
QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
topWindowChanged(w);
topVisibleWindowChanged();
}
void QAndroidPlatformScreen::setPhysicalSize(const QSize &size)
@ -284,13 +280,14 @@ void QAndroidPlatformScreen::applicationStateChanged(Qt::ApplicationState state)
w->applicationStateChanged(state);
}
void QAndroidPlatformScreen::topWindowChanged(QWindow *w)
void QAndroidPlatformScreen::topVisibleWindowChanged()
{
QWindow *w = topVisibleWindow();
QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
QtAndroidMenu::setActiveTopLevelWindow(w);
if (w != 0) {
if (w && w->handle()) {
QAndroidPlatformWindow *platformWindow = static_cast<QAndroidPlatformWindow *>(w->handle());
if (platformWindow != 0)
if (platformWindow)
platformWindow->updateSystemUiVisibility();
}
}

View File

@ -37,7 +37,7 @@ public:
int currentMode() const override { return m_currentMode; }
int preferredMode() const override { return m_currentMode; }
qreal refreshRate() const override { return m_refreshRate; }
inline QWindow *topWindow() const;
inline QWindow *topVisibleWindow() const;
QWindow *topLevelAt(const QPoint & p) const override;
void addWindow(QAndroidPlatformWindow *window);
@ -45,7 +45,7 @@ public:
void raise(QAndroidPlatformWindow *window);
void lower(QAndroidPlatformWindow *window);
void topWindowChanged(QWindow *w);
void topVisibleWindowChanged();
int displayId() const override;
public slots:
@ -60,7 +60,6 @@ public slots:
protected:
typedef QList<QAndroidPlatformWindow *> WindowStackType;
WindowStackType m_windowStack;
QRect m_availableGeometry;
int m_depth;
QImage::Format m_format;

View File

@ -94,7 +94,7 @@ VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
clearSurface();
QMutexLocker lock(&m_surfaceMutex);
if (m_nativeSurfaceId == -1) {
if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return &m_vkSurface;
@ -102,7 +102,7 @@ VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
m_surfaceWaitCondition.wait(&m_surfaceMutex);
}
if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return &m_vkSurface;
QJniEnvironment env;

View File

@ -14,11 +14,15 @@
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
Q_CONSTINIT static QBasicAtomicInt winIdGenerator = Q_BASIC_ATOMIC_INITIALIZER(0);
QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
: QPlatformWindow(window), m_androidSurfaceObject(nullptr)
: QPlatformWindow(window), m_nativeQtWindow(QNativeInterface::QAndroidApplication::context()),
m_androidSurfaceObject(nullptr)
{
m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
m_windowFlags = Qt::Widget;
m_windowState = Qt::WindowNoState;
// the surfaceType is overwritten in QAndroidPlatformOpenGLWindow ctor so let's save
@ -44,8 +48,15 @@ QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
if (requestedNativeGeometry != finalNativeGeometry)
setGeometry(finalNativeGeometry);
}
platformScreen()->addWindow(this);
}
QAndroidPlatformWindow::~QAndroidPlatformWindow()
{
platformScreen()->removeWindow(this);
}
void QAndroidPlatformWindow::lower()
{
platformScreen()->lower(this);
@ -76,23 +87,19 @@ void QAndroidPlatformWindow::setGeometry(const QRect &rect)
void QAndroidPlatformWindow::setVisible(bool visible)
{
if (visible)
updateSystemUiVisibility();
if (visible) {
updateSystemUiVisibility();
if ((m_windowState & Qt::WindowFullScreen)
|| ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint))) {
setGeometry(platformScreen()->geometry());
} else if (m_windowState & Qt::WindowMaximized) {
setGeometry(platformScreen()->availableGeometry());
}
requestActivateWindow();
} else if (window() == qGuiApp->focusWindow()) {
platformScreen()->topVisibleWindowChanged();
}
if (visible)
platformScreen()->addWindow(this);
else
platformScreen()->removeWindow(this);
QRect availableGeometry = screen()->availableGeometry();
if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
QPlatformWindow::setVisible(visible);
@ -125,7 +132,14 @@ Qt::WindowFlags QAndroidPlatformWindow::windowFlags() const
void QAndroidPlatformWindow::setParent(const QPlatformWindow *window)
{
Q_UNUSED(window);
// even though we do not yet support child windows properly, any windows getting a parent
// should be removed from screen's window stack which is only for top level windows,
// and respectively any window becoming top level should go in there
if (window) {
platformScreen()->removeWindow(this);
} else {
platformScreen()->addWindow(this);
}
}
QAndroidPlatformScreen *QAndroidPlatformWindow::platformScreen() const
@ -140,7 +154,8 @@ void QAndroidPlatformWindow::propagateSizeHints()
void QAndroidPlatformWindow::requestActivateWindow()
{
platformScreen()->topWindowChanged(window());
if (!blockedByModal())
raise();
}
void QAndroidPlatformWindow::updateSystemUiVisibility()
@ -176,25 +191,62 @@ void QAndroidPlatformWindow::applicationStateChanged(Qt::ApplicationState)
void QAndroidPlatformWindow::createSurface()
{
const QRect rect = geometry();
jint x = 0, y = 0, w = -1, h = -1;
if (!rect.isNull()) {
x = rect.x();
y = rect.y();
w = std::max(rect.width(), 1);
h = std::max(rect.height(), 1);
}
const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32);
m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, x, y, w, h, 32);
m_surfaceCreated = true;
}
void QAndroidPlatformWindow::destroySurface()
{
if (m_nativeSurfaceId != -1) {
QtAndroid::destroySurface(m_nativeSurfaceId);
m_nativeSurfaceId = -1;
if (m_surfaceCreated) {
m_nativeQtWindow.callMethod<void>("destroySurface");
m_surfaceCreated = false;
}
}
void QAndroidPlatformWindow::setSurfaceGeometry(const QRect &rect)
void QAndroidPlatformWindow::setSurfaceGeometry(const QRect &geometry)
{
if (m_nativeSurfaceId != -1)
QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect);
if (!m_surfaceCreated)
return;
jint x = 0;
jint y = 0;
jint w = -1;
jint h = -1;
if (!geometry.isNull()) {
x = geometry.x();
y = geometry.y();
w = geometry.width();
h = geometry.height();
}
m_nativeQtWindow.callMethod<void>("setSurfaceGeometry", x, y, w, h);
}
void QAndroidPlatformWindow::sendExpose()
void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
{
lockSurface();
m_androidSurfaceObject = surface;
if (m_androidSurfaceObject.isValid()) // wait until we have a valid surface to draw into
m_surfaceWaitCondition.wakeOne();
unlockSurface();
if (m_androidSurfaceObject.isValid()) {
// repaint the window, when we have a valid surface
sendExpose();
}
}
void QAndroidPlatformWindow::sendExpose() const
{
QRect availableGeometry = screen()->availableGeometry();
if (!geometry().isNull() && !availableGeometry.isNull()) {
@ -203,15 +255,41 @@ void QAndroidPlatformWindow::sendExpose()
}
}
void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
bool QAndroidPlatformWindow::blockedByModal() const
{
lockSurface();
m_androidSurfaceObject = surface;
if (m_androidSurfaceObject.isValid())
m_surfaceWaitCondition.wakeOne();
unlockSurface();
if (m_androidSurfaceObject.isValid())
sendExpose();
QWindow *modalWindow = QGuiApplication::modalWindow();
return modalWindow && modalWindow != window();
}
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());
if (platformWindow->nativeViewId() == windowId)
platformWindow->onSurfaceChanged(surface);
}
}
bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env)
{
if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtWindow>::className(),
{Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow)})) {
qCCritical(lcQpaWindow) << "RegisterNatives failed for"
<< QtJniTypes::Traits<QtJniTypes::QtWindow>::className();
return false;
}
return true;
}
QT_END_NAMESPACE

View File

@ -17,6 +17,8 @@
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")
class QAndroidPlatformScreen;
@ -25,7 +27,7 @@ class QAndroidPlatformWindow: public QPlatformWindow
{
public:
explicit QAndroidPlatformWindow(QWindow *window);
~QAndroidPlatformWindow();
void lower() override;
void raise() override;
@ -49,9 +51,12 @@ public:
void updateSystemUiVisibility();
inline bool isRaster() const { return m_isRaster; }
bool isExposed() const override;
QtJniTypes::QtWindow nativeWindow() const { return m_nativeQtWindow; }
virtual void applicationStateChanged(Qt::ApplicationState);
int nativeViewId() const { return m_nativeViewId; }
static bool registerNatives(QJniEnvironment &env);
void onSurfaceChanged(QtJniTypes::Surface surface);
protected:
@ -60,20 +65,27 @@ protected:
void unlockSurface() { m_surfaceMutex.unlock(); }
void createSurface();
void destroySurface();
void setSurfaceGeometry(const QRect &rect);
void sendExpose();
void setSurfaceGeometry(const QRect &geometry);
void sendExpose() const;
bool blockedByModal() const;
protected:
Qt::WindowFlags m_windowFlags;
Qt::WindowStates m_windowState;
bool m_isRaster;
WId m_windowId;
QtJniTypes::QtWindow m_nativeQtWindow;
// The Android Surface, accessed from multiple threads, guarded by m_surfaceMutex
QtJniTypes::Surface m_androidSurfaceObject;
QWaitCondition m_surfaceWaitCondition;
int m_nativeSurfaceId = -1;
int m_nativeViewId = -1;
bool m_surfaceCreated = false;
QMutex m_surfaceMutex;
private:
static void setSurface(JNIEnv *env, jobject obj, jint windowId, QtJniTypes::Surface surface);
Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setSurface)
};
QT_END_NAMESPACE