Android: Use TextureView when multiple windows present

The SurfaceView class is not the best option for when we
have multiple windows, as the created Surface can only
either be below the window or on top of it, it is not a
part of the view hierarchy.

Replace the SurfaceView with TextureView when there are
more than one window. This way the surface will be a part
of the view hierarchy, and will respect z-ordering.

When there is only one window, keep using the SurfaceView
approach to limit the effect on existing apps, as well
as enable some of the benefits SurfaceView has for e.g.
game and multimedia apps, such as HDR ability.

Move touch handling from QtSurface to QtWindow, so
touches are handled also when using TextureView instead
of QtSurface aka SurfaceView.

Pick-to: 6.7
Task-number: QTBUG-118142
Change-Id: I37dcaf0fb656cfc1ff2eca0a3bfe7259f607411c
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Tinja Paavoseppä 2023-10-17 11:47:49 +03:00 committed by Soheil Armin
parent b2e44a2d1d
commit cca81a6636
8 changed files with 177 additions and 71 deletions

View File

@ -21,7 +21,9 @@ set(java_sources
src/org/qtproject/qt/android/QtLayout.java
src/org/qtproject/qt/android/QtMessageDialogHelper.java
src/org/qtproject/qt/android/QtNative.java
src/org/qtproject/qt/android/QtSurfaceInterface.java
src/org/qtproject/qt/android/QtSurface.java
src/org/qtproject/qt/android/QtTextureView.java
src/org/qtproject/qt/android/QtThread.java
src/org/qtproject/qt/android/extras/QtAndroidBinder.java
src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java

View File

@ -10,16 +10,31 @@ import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
class QtLayout extends ViewGroup
{
class QtLayout extends ViewGroup {
interface QtTouchListener {
public boolean onTouchEvent(MotionEvent event);
public boolean onTrackballEvent(MotionEvent event);
public boolean onGenericMotionEvent(MotionEvent event);
}
private QtTouchListener m_touchListener;
public QtLayout(Context context)
{
super(context);
}
public QtLayout(Context context, QtTouchListener listener)
{
super(context);
m_touchListener = listener;
}
public QtLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
@ -30,6 +45,30 @@ class QtLayout extends ViewGroup
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (m_touchListener != null) {
event.setLocation(event.getX() + getX(), event.getY() + getY());
return m_touchListener.onTouchEvent(event);
}
return false;
}
@Override
public boolean onTrackballEvent(MotionEvent event)
{;
if (m_touchListener != null)
return m_touchListener.onTrackballEvent(event);
return false;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
if (m_touchListener != null)
return m_touchListener.onGenericMotionEvent(event);
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{

View File

@ -7,8 +7,6 @@ package org.qtproject.qt.android;
import android.annotation.SuppressLint;
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,17 +14,9 @@ import android.view.SurfaceView;
@SuppressLint("ViewConstructor")
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;
private QtSurfaceInterface m_surfaceCallback;
interface SurfaceChangedCallback {
void onSurfaceChanged(Surface surface);
}
public QtSurface(Context context, SurfaceChangedCallback surfaceCallback, int id, boolean onTop,
int imageDepth)
public QtSurface(Context context, QtSurfaceInterface surfaceCallback, boolean onTop, int imageDepth)
{
super(context);
setFocusable(false);
@ -38,15 +28,6 @@ class QtSurface extends SurfaceView implements SurfaceHolder.Callback
getHolder().setFormat(PixelFormat.RGB_565);
else
getHolder().setFormat(PixelFormat.RGBA_8888);
m_windowId = id;
m_gestureDetector =
new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent event) {
QtInputDelegate.longPress(m_windowId, (int) event.getX(), (int) event.getY());
}
});
m_gestureDetector.setIsLongpressEnabled(true);
}
@Override
@ -69,30 +50,4 @@ class QtSurface extends SurfaceView implements SurfaceHolder.Callback
if (m_surfaceCallback != null)
m_surfaceCallback.onSurfaceChanged(null);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
// QTBUG-65927
// Fix event positions depending on Surface position.
// 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, m_windowId);
m_gestureDetector.onTouchEvent(event);
return true;
}
@Override
public boolean onTrackballEvent(MotionEvent event)
{
QtInputDelegate.sendTrackballEvent(event, m_windowId);
return true;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
return QtInputDelegate.sendGenericMotionEvent(event, m_windowId);
}
}

View File

@ -0,0 +1,13 @@
// 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
package org.qtproject.qt.android;
import android.view.Surface;
public interface QtSurfaceInterface
{
void onSurfaceChanged(Surface surface);
}

View File

@ -0,0 +1,52 @@
// 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
package org.qtproject.qt.android;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
public class QtTextureView extends TextureView implements TextureView.SurfaceTextureListener
{
private QtSurfaceInterface m_surfaceCallback;
private boolean m_staysOnTop;
private Surface m_surface;
public QtTextureView(Context context, QtSurfaceInterface surfaceCallback, boolean isOpaque)
{
super(context);
setFocusable(false);
setFocusableInTouchMode(false);
m_surfaceCallback = surfaceCallback;
setSurfaceTextureListener(this);
setOpaque(isOpaque);
setSurfaceTexture(new SurfaceTexture(false));
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
m_surface = new Surface(surfaceTexture);
m_surfaceCallback.onSurfaceChanged(m_surface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
m_surface = new Surface(surfaceTexture);
m_surfaceCallback.onSurfaceChanged(m_surface);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
m_surfaceCallback.onSurfaceChanged(null);
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
}

View File

@ -4,6 +4,8 @@
package org.qtproject.qt.android;
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.util.Log;
import android.view.Surface;
import android.view.View;
@ -11,15 +13,16 @@ import android.view.ViewGroup;
import java.util.HashMap;
public class QtWindow implements QtSurface.SurfaceChangedCallback {
public class QtWindow implements QtSurfaceInterface, QtLayout.QtTouchListener {
private final static String TAG = "QtWindow";
private View m_surfaceContainer;
private QtLayout m_layout;
private QtSurface m_surface;
private View m_nativeView;
private HashMap<Integer, QtWindow> m_childWindows = new HashMap<Integer, QtWindow>();
private QtWindow m_parentWindow;
private int m_id;
private GestureDetector m_gestureDetector;
private static native void setSurface(int windowId, Surface surface);
@ -27,8 +30,15 @@ public class QtWindow implements QtSurface.SurfaceChangedCallback {
{
m_id = View.generateViewId();
QtNative.runAction(() -> {
m_layout = new QtLayout(context);
m_layout = new QtLayout(context, this);
setParent(parentWindow);
m_gestureDetector =
new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent event) {
QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY());
}
});
m_gestureDetector.setIsLongpressEnabled(true);
});
}
@ -57,6 +67,27 @@ public class QtWindow implements QtSurface.SurfaceChangedCallback {
setSurface(getId(), surface);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
QtInputDelegate.sendTouchEvent(event, getId());
m_gestureDetector.onTouchEvent(event);
return true;
}
@Override
public boolean onTrackballEvent(MotionEvent event)
{
QtInputDelegate.sendTrackballEvent(event, getId());
return true;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
return QtInputDelegate.sendGenericMotionEvent(event, getId());
}
public void removeWindow()
{
if (m_parentWindow != null)
@ -65,30 +96,28 @@ public class QtWindow implements QtSurface.SurfaceChangedCallback {
public void createSurface(final boolean onTop,
final int x, final int y, final int w, final int h,
final int imageDepth)
final int imageDepth, final boolean isOpaque,
final int surfaceContainerType) // TODO constant for type
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_surface != null)
m_layout.removeView(m_surface);
if (m_surfaceContainer != null)
m_layout.removeView(m_surfaceContainer);
m_layout.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
// TODO currently setting child windows to onTop, since their surfaces
// now get created earlier than the parents -> they are behind the parent window
// without this, and SurfaceView z-ordering is limited
boolean tempOnTop = onTop || (m_parentWindow != null);
QtSurface surface = new QtSurface(m_layout.getContext(), QtWindow.this,
QtWindow.this.getId(), tempOnTop, imageDepth);
surface.setLayoutParams(new QtLayout.LayoutParams(
if (surfaceContainerType == 0) {
m_surfaceContainer = new QtSurface(m_layout.getContext(), QtWindow.this,
onTop, imageDepth);
} else {
m_surfaceContainer = new QtTextureView(m_layout.getContext(), QtWindow.this, isOpaque);
}
m_surfaceContainer.setLayoutParams(new QtLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// The QtSurface of this window will be added as the first of the stack.
// The surface container of this window will be added as the first of the stack.
// All other views are stacked based on the order they are created.
m_layout.addView(surface, 0);
m_surface = surface;
m_layout.addView(m_surfaceContainer, 0);
}
});
}
@ -98,9 +127,9 @@ public class QtWindow implements QtSurface.SurfaceChangedCallback {
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_surface != null) {
m_layout.removeView(m_surface);
m_surface = null;
if (m_surfaceContainer != null) {
m_layout.removeView(m_surfaceContainer);
m_surfaceContainer = null;
}
}
});

View File

@ -55,6 +55,14 @@ QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
if (window->isTopLevel())
platformScreen()->addWindow(this);
// 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)
if (platformScreen()->windows().size() > 1)
m_surfaceContainerType = SurfaceContainer::TextureView;
else
m_surfaceContainerType = SurfaceContainer::SurfaceView;
}
QAndroidPlatformWindow::~QAndroidPlatformWindow()
@ -236,8 +244,10 @@ void QAndroidPlatformWindow::createSurface()
}
const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
const bool isOpaque = format().hasAlpha() || (0.0 < window()->opacity() < 1.0);
m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, x, y, w, h, 32);
m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, x, y, w, h, 32, isOpaque,
m_surfaceContainerType);
m_surfaceCreated = true;
}

View File

@ -26,6 +26,11 @@ class QAndroidPlatformScreen;
class QAndroidPlatformWindow: public QPlatformWindow
{
public:
enum class SurfaceContainer {
SurfaceView,
TextureView
};
explicit QAndroidPlatformWindow(QWindow *window);
~QAndroidPlatformWindow();
void lower() override;
@ -76,6 +81,7 @@ protected:
int m_nativeViewId = -1;
QtJniTypes::QtWindow m_nativeQtWindow;
SurfaceContainer m_surfaceContainerType = SurfaceContainer::SurfaceView;
QtJniTypes::QtWindow m_nativeParentQtWindow;
// The Android Surface, accessed from multiple threads, guarded by m_surfaceMutex
QtJniTypes::Surface m_androidSurfaceObject;