Android: Add classes to embed QML into native Android
Add classes that make it possible to add QML as a View into a native Android app: QtView: Base class for QtQuickView, handles non-Quick dependent operations. In essence a Java ViewGroup class which loads a QWindow and embeds it into itself. QtEmbeddedLoader: Extends QtLoader for embedded case, creates the embedded version of QtActivityDelegate and provides an embedded-specific path to loading Qt libraries (Mostly just allows users to set the name of the main lib) QtAndroidWindowEmbedding namespace: Deals with calls from QtEmbeddedDelegate to create/destroy QWindow and from QtView to show the window. Take the QtEmbeddedDelegate introduced in an earlier commit into use, and add functionality for loading QWindows for QtViews and managing QtViews into it. Add a factory for creating instances of QtEmbeddedDelegate. The factory holds a map of QtEmbeddedDelegate objects and creates them, with the Activity as the key. This is to make it so that the same delegate can be used by multiple views which share the same Context. Known issues left: * keyboard focus not working, as with other child windows Pick-to: 6.7 Task-number: QTBUG-118872 Change-Id: I94a5f9b4f904c05cc6368cf20f273fcf10d31f17 Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
parent
b3441b4127
commit
702c420f51
@ -35,6 +35,9 @@ set(java_sources
|
|||||||
src/org/qtproject/qt/android/QtWindow.java
|
src/org/qtproject/qt/android/QtWindow.java
|
||||||
src/org/qtproject/qt/android/QtActivityDelegateBase.java
|
src/org/qtproject/qt/android/QtActivityDelegateBase.java
|
||||||
src/org/qtproject/qt/android/QtEmbeddedDelegate.java
|
src/org/qtproject/qt/android/QtEmbeddedDelegate.java
|
||||||
|
src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java
|
||||||
|
src/org/qtproject/qt/android/QtEmbeddedLoader.java
|
||||||
|
src/org/qtproject/qt/android/QtView.java
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android
|
qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2023 The Qt Company Ltd.
|
// Copyright (C) 2024 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
|
// 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;
|
package org.qtproject.qt.android;
|
||||||
@ -22,8 +22,14 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppStateDetailsListener {
|
class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppStateDetailsListener {
|
||||||
|
// TODO simplistic implementation with one QtView, expand to support multiple views QTBUG-117649
|
||||||
|
private QtView m_view;
|
||||||
|
private long m_rootWindowRef = 0L;
|
||||||
private QtNative.ApplicationStateDetails m_stateDetails;
|
private QtNative.ApplicationStateDetails m_stateDetails;
|
||||||
|
|
||||||
|
private static native void createRootWindow(View rootView);
|
||||||
|
static native void deleteWindow(long windowReference);
|
||||||
|
|
||||||
public QtEmbeddedDelegate(Activity context) {
|
public QtEmbeddedDelegate(Activity context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
@ -71,6 +77,7 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
|
|||||||
if (m_activity == activity && m_stateDetails.isStarted) {
|
if (m_activity == activity && m_stateDetails.isStarted) {
|
||||||
m_activity.getApplication().unregisterActivityLifecycleCallbacks(this);
|
m_activity.getApplication().unregisterActivityLifecycleCallbacks(this);
|
||||||
QtNative.unregisterAppStateListener(QtEmbeddedDelegate.this);
|
QtNative.unregisterAppStateListener(QtEmbeddedDelegate.this);
|
||||||
|
QtEmbeddedDelegateFactory.remove(m_activity);
|
||||||
QtNative.terminateQt();
|
QtNative.terminateQt();
|
||||||
QtNative.setActivity(null);
|
QtNative.setActivity(null);
|
||||||
QtNative.getQtThread().exit();
|
QtNative.getQtThread().exit();
|
||||||
@ -89,6 +96,8 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
|
|||||||
QtDisplayManager.setApplicationDisplayMetrics(m_activity,
|
QtDisplayManager.setApplicationDisplayMetrics(m_activity,
|
||||||
metrics.widthPixels,
|
metrics.widthPixels,
|
||||||
metrics.heightPixels);
|
metrics.heightPixels);
|
||||||
|
if (m_view != null)
|
||||||
|
createRootWindow(m_view);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,11 +120,30 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
|
|||||||
@Override
|
@Override
|
||||||
QtLayout getQtLayout()
|
QtLayout getQtLayout()
|
||||||
{
|
{
|
||||||
// TODO could probably use QtView here when it's added?
|
// TODO verify if returning m_view here works, this is used by the androidjniinput
|
||||||
return null;
|
// when e.g. showing a keyboard, so depends on getting the keyboard focus working
|
||||||
|
// QTBUG-118873
|
||||||
|
return m_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueLoadWindow()
|
||||||
|
{
|
||||||
|
if (m_stateDetails.nativePluginIntegrationReady) {
|
||||||
|
createRootWindow(m_view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setView(QtView view) {
|
||||||
|
m_view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootWindowRef(long ref) {
|
||||||
|
m_rootWindowRef = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
// TODO delete the window once it's added
|
if (m_rootWindowRef != 0L)
|
||||||
|
deleteWindow(m_rootWindowRef);
|
||||||
|
m_rootWindowRef = 0L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (C) 2024 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.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
class QtEmbeddedDelegateFactory {
|
||||||
|
private static final HashMap<Activity, QtEmbeddedDelegate> m_delegates = new HashMap<>();
|
||||||
|
private static final Object m_delegateLock = new Object();
|
||||||
|
|
||||||
|
@UsedFromNativeCode
|
||||||
|
public static QtActivityDelegateBase getActivityDelegate(Activity activity) {
|
||||||
|
synchronized (m_delegateLock) {
|
||||||
|
return m_delegates.get(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QtEmbeddedDelegate create(Activity activity) {
|
||||||
|
synchronized (m_delegateLock) {
|
||||||
|
if (!m_delegates.containsKey(activity))
|
||||||
|
m_delegates.put(activity, new QtEmbeddedDelegate(activity));
|
||||||
|
|
||||||
|
return m_delegates.get(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove(Activity activity) {
|
||||||
|
synchronized (m_delegateLock) {
|
||||||
|
m_delegates.remove(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2024 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.app.Activity;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import dalvik.system.DexClassLoader;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
class QtEmbeddedLoader extends QtLoader {
|
||||||
|
private static final String TAG = "QtEmbeddedLoader";
|
||||||
|
|
||||||
|
public QtEmbeddedLoader(Context context) {
|
||||||
|
super(new ContextWrapper(context));
|
||||||
|
// TODO Service context handling QTBUG-118874
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
// Called when loading fails - clear the delegate to make sure we don't hold reference
|
||||||
|
// to the embedding Context
|
||||||
|
QtEmbeddedDelegateFactory.remove((Activity)m_context.getBaseContext());
|
||||||
|
}
|
||||||
|
}
|
130
src/android/jar/src/org/qtproject/qt/android/QtView.java
Normal file
130
src/android/jar/src/org/qtproject/qt/android/QtView.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (C) 2024 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.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
// TODO this should not need to extend QtLayout, a simple FrameLayout/ViewGroup should do
|
||||||
|
// QTBUG-121516
|
||||||
|
// Base class for embedding QWindow into native Android view hierarchy. Extend to implement
|
||||||
|
// the creation of appropriate window to embed.
|
||||||
|
abstract class QtView extends QtLayout {
|
||||||
|
private final static String TAG = "QtView";
|
||||||
|
|
||||||
|
public interface QtWindowListener {
|
||||||
|
// Called when the QWindow has been created and it's Java counterpart embedded into
|
||||||
|
// QtView
|
||||||
|
void onQtWindowLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QtWindow m_window;
|
||||||
|
protected long m_windowReference;
|
||||||
|
protected QtWindowListener m_windowListener;
|
||||||
|
protected QtEmbeddedDelegate m_delegate;
|
||||||
|
// Implement in subclass to handle the creation of the QWindow and its parent container.
|
||||||
|
// TODO could we take care of the parent window creation and parenting outside of the
|
||||||
|
// window creation method to simplify things if user would extend this? Preferably without
|
||||||
|
// too much JNI back and forth. Related to parent window creation, so handle with QTBUG-121511.
|
||||||
|
abstract protected void createWindow(long parentWindowRef);
|
||||||
|
|
||||||
|
private static native void setWindowVisible(long windowReference, boolean visible);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create QtView for embedding a QWindow. Instantiating a QtView will load the Qt libraries
|
||||||
|
* if they have not already been loaded, including the app library specified by appName, and
|
||||||
|
* starting the said Qt app.
|
||||||
|
* @param context the hosting Context
|
||||||
|
* @param appLibName the name of the Qt app library to load and start. This corresponds to the
|
||||||
|
target name set in Qt app's CMakeLists.txt
|
||||||
|
**/
|
||||||
|
public QtView(Context context, String appLibName) throws InvalidParameterException {
|
||||||
|
super(context);
|
||||||
|
if (appLibName == null || appLibName.isEmpty()) {
|
||||||
|
throw new InvalidParameterException("QtView: argument 'appLibName' may not be empty "+
|
||||||
|
"or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
QtEmbeddedLoader loader = new QtEmbeddedLoader(context);
|
||||||
|
m_delegate = QtEmbeddedDelegateFactory.create((Activity)context);
|
||||||
|
loader.setMainLibraryName(appLibName);
|
||||||
|
loader.loadQtLibraries();
|
||||||
|
// Start Native Qt application
|
||||||
|
m_delegate.startNativeApplication(loader.getApplicationParameters(),
|
||||||
|
loader.getMainLibraryPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
m_delegate.setView(this);
|
||||||
|
m_delegate.queueLoadWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
destroyWindow();
|
||||||
|
m_delegate.setView(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQtWindowListener(QtWindowListener listener) {
|
||||||
|
m_windowListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWindowReference(long windowReference) {
|
||||||
|
m_windowReference = windowReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
long windowReference() {
|
||||||
|
return m_windowReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the visibility of the underlying QWindow. If visible is true, showNormal() is called.
|
||||||
|
// If false, the window is hidden.
|
||||||
|
void setWindowVisible(boolean visible) {
|
||||||
|
if (m_windowReference != 0L)
|
||||||
|
setWindowVisible(m_windowReference, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from Qt when the QWindow has been created.
|
||||||
|
// window - the Java QtWindow of the created QAndroidPlatformWindow, to embed into the QtView
|
||||||
|
// viewReference - the reference to the created QQuickView
|
||||||
|
void addQtWindow(QtWindow window, long viewReference, long parentWindowRef) {
|
||||||
|
setWindowReference(viewReference);
|
||||||
|
m_delegate.setRootWindowRef(parentWindowRef);
|
||||||
|
final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
m_window = window;
|
||||||
|
m_window.getLayout().setLayoutParams(new QtLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
addView(m_window.getLayout(), 0);
|
||||||
|
// Call show window + parent
|
||||||
|
setWindowVisible(true);
|
||||||
|
if (m_windowListener != null)
|
||||||
|
m_windowListener.onQtWindowLoaded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the underlying QWindow
|
||||||
|
void destroyWindow() {
|
||||||
|
if (m_windowReference != 0L)
|
||||||
|
QtEmbeddedDelegate.deleteWindow(m_windowReference);
|
||||||
|
m_windowReference = 0L;
|
||||||
|
}
|
||||||
|
}
|
@ -140,7 +140,8 @@ public class QtWindow implements QtSurfaceInterface, QtLayout.QtTouchListener {
|
|||||||
QtNative.runAction(new Runnable() {
|
QtNative.runAction(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
m_layout.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
|
if (m_layout.getContext() instanceof QtActivityBase)
|
||||||
|
m_layout.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
|
|||||||
qandroidplatformtheme.cpp qandroidplatformtheme.h
|
qandroidplatformtheme.cpp qandroidplatformtheme.h
|
||||||
qandroidplatformwindow.cpp qandroidplatformwindow.h
|
qandroidplatformwindow.cpp qandroidplatformwindow.h
|
||||||
qandroidsystemlocale.cpp qandroidsystemlocale.h
|
qandroidsystemlocale.cpp qandroidsystemlocale.h
|
||||||
|
androidwindowembedding.cpp androidwindowembedding.h
|
||||||
NO_UNITY_BUILD_SOURCES
|
NO_UNITY_BUILD_SOURCES
|
||||||
# Conflicting symbols and macros with androidjnimain.cpp
|
# Conflicting symbols and macros with androidjnimain.cpp
|
||||||
# TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar
|
# TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "androidjniinput.h"
|
#include "androidjniinput.h"
|
||||||
#include "androidjnimain.h"
|
#include "androidjnimain.h"
|
||||||
#include "androidjnimenu.h"
|
#include "androidjnimenu.h"
|
||||||
|
#include "androidwindowembedding.h"
|
||||||
#include "qandroidassetsfileenginehandler.h"
|
#include "qandroidassetsfileenginehandler.h"
|
||||||
#include "qandroideventdispatcher.h"
|
#include "qandroideventdispatcher.h"
|
||||||
#include "qandroidplatformdialoghelpers.h"
|
#include "qandroidplatformdialoghelpers.h"
|
||||||
@ -92,6 +93,8 @@ static const char m_methodErrorMsg[] = "Can't find method \"%s%s\"";
|
|||||||
|
|
||||||
Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
|
Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
|
||||||
|
|
||||||
|
Q_DECLARE_JNI_CLASS(QtEmbeddedDelegateFactory, "org/qtproject/qt/android/QtEmbeddedDelegateFactory")
|
||||||
|
|
||||||
namespace QtAndroid
|
namespace QtAndroid
|
||||||
{
|
{
|
||||||
QBasicMutex *platformInterfaceMutex()
|
QBasicMutex *platformInterfaceMutex()
|
||||||
@ -187,10 +190,17 @@ namespace QtAndroid
|
|||||||
// FIXME: avoid direct access to QtActivityDelegate
|
// FIXME: avoid direct access to QtActivityDelegate
|
||||||
QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
|
QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
|
||||||
{
|
{
|
||||||
|
using namespace QtJniTypes;
|
||||||
if (!m_activityDelegate.isValid()) {
|
if (!m_activityDelegate.isValid()) {
|
||||||
auto activity = QtAndroidPrivate::activity();
|
if (isQtApplication()) {
|
||||||
m_activityDelegate = activity.callMethod<QtJniTypes::QtActivityDelegateBase>(
|
auto context = QtAndroidPrivate::activity();
|
||||||
"getActivityDelegate");
|
m_activityDelegate = context.callMethod<QtActivityDelegateBase>("getActivityDelegate");
|
||||||
|
} else {
|
||||||
|
m_activityDelegate = QJniObject::callStaticMethod<QtActivityDelegateBase>(
|
||||||
|
Traits<QtEmbeddedDelegateFactory>::className(),
|
||||||
|
"getActivityDelegate",
|
||||||
|
QtAndroidPrivate::activity());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_activityDelegate;
|
return m_activityDelegate;
|
||||||
@ -213,11 +223,15 @@ namespace QtAndroid
|
|||||||
// embedded into a native Android app, where the Activity/Service is created
|
// embedded into a native Android app, where the Activity/Service is created
|
||||||
// by the user, outside of Qt, and Qt content is added as a view.
|
// by the user, outside of Qt, and Qt content is added as a view.
|
||||||
JNIEnv *env = QJniEnvironment::getJniEnv();
|
JNIEnv *env = QJniEnvironment::getJniEnv();
|
||||||
static const jint isQtActivity = env->IsInstanceOf(QtAndroidPrivate::activity().object(),
|
auto activity = QtAndroidPrivate::activity();
|
||||||
m_qtActivityClass);
|
if (activity.isValid())
|
||||||
static const jint isQtService = env->IsInstanceOf(QtAndroidPrivate::service().object(),
|
return env->IsInstanceOf(activity.object(), m_qtActivityClass);
|
||||||
m_qtServiceClass);
|
auto service = QtAndroidPrivate::service();
|
||||||
return isQtActivity || isQtService;
|
if (service.isValid())
|
||||||
|
return env->IsInstanceOf(QtAndroidPrivate::service().object(), m_qtServiceClass);
|
||||||
|
// return true as default as Qt application is our default use case.
|
||||||
|
// famous last words: we should not end up here
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyAccessibilityLocationChange(uint accessibilityObjectId)
|
void notifyAccessibilityLocationChange(uint accessibilityObjectId)
|
||||||
@ -870,7 +884,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
|
|||||||
|| !QtAndroidAccessibility::registerNatives(env)
|
|| !QtAndroidAccessibility::registerNatives(env)
|
||||||
|| !QtAndroidDialogHelpers::registerNatives(env)
|
|| !QtAndroidDialogHelpers::registerNatives(env)
|
||||||
|| !QAndroidPlatformClipboard::registerNatives(env)
|
|| !QAndroidPlatformClipboard::registerNatives(env)
|
||||||
|| !QAndroidPlatformWindow::registerNatives(env)) {
|
|| !QAndroidPlatformWindow::registerNatives(env)
|
||||||
|
|| !QtAndroidWindowEmbedding::registerNatives(env)) {
|
||||||
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
|
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
61
src/plugins/platforms/android/androidwindowembedding.cpp
Normal file
61
src/plugins/platforms/android/androidwindowembedding.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#include "androidwindowembedding.h"
|
||||||
|
|
||||||
|
#include <QtCore/qcoreapplication.h>
|
||||||
|
#include <QtCore/qjnienvironment.h>
|
||||||
|
#include <QtCore/qjniobject.h>
|
||||||
|
#include <QtCore/qjnitypes.h>
|
||||||
|
#include <QtGui/qwindow.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
Q_DECLARE_JNI_CLASS(QtView, "org/qtproject/qt/android/QtView");
|
||||||
|
Q_DECLARE_JNI_CLASS(QtEmbeddedDelegate, "org/qtproject/qt/android/QtEmbeddedDelegate");
|
||||||
|
|
||||||
|
namespace QtAndroidWindowEmbedding {
|
||||||
|
void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView)
|
||||||
|
{
|
||||||
|
// QWindow should be constructed on the Qt thread rather than directly in the caller thread
|
||||||
|
// To avoid hitting checkReceiverThread assert in QCoreApplication::doNotify
|
||||||
|
QMetaObject::invokeMethod(qApp, [rootView] {
|
||||||
|
QWindow *parentWindow = QWindow::fromWinId(reinterpret_cast<WId>(rootView.object()));
|
||||||
|
rootView.callMethod<void>("createWindow", reinterpret_cast<jlong>(parentWindow));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteWindow(JNIEnv *, jclass, jlong windowRef)
|
||||||
|
{
|
||||||
|
QWindow *window = reinterpret_cast<QWindow*>(windowRef);
|
||||||
|
window->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWindowVisible(JNIEnv *, jclass, jlong windowRef, jboolean visible)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(qApp, [windowRef, visible] {
|
||||||
|
QWindow *window = reinterpret_cast<QWindow*>(windowRef);
|
||||||
|
if (visible) {
|
||||||
|
window->showNormal();
|
||||||
|
if (!window->parent()->isVisible())
|
||||||
|
window->parent()->show();
|
||||||
|
} else {
|
||||||
|
window->hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool registerNatives(QJniEnvironment& env) {
|
||||||
|
using namespace QtJniTypes;
|
||||||
|
bool success = env.registerNativeMethods(Traits<QtEmbeddedDelegate>::className(),
|
||||||
|
{Q_JNI_NATIVE_SCOPED_METHOD(createRootWindow, QtAndroidWindowEmbedding),
|
||||||
|
Q_JNI_NATIVE_SCOPED_METHOD(deleteWindow, QtAndroidWindowEmbedding)});
|
||||||
|
|
||||||
|
success &= env.registerNativeMethods(Traits<QtView>::className(),
|
||||||
|
{Q_JNI_NATIVE_SCOPED_METHOD(setWindowVisible, QtAndroidWindowEmbedding)});
|
||||||
|
return success;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
38
src/plugins/platforms/android/androidwindowembedding.h
Normal file
38
src/plugins/platforms/android/androidwindowembedding.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifndef QTANDROIDWINDOWEMBEDDING_H
|
||||||
|
#define QTANDROIDWINDOWEMBEDDING_H
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G
|
||||||
|
// -------------
|
||||||
|
//
|
||||||
|
// This file is not part of the Qt API. It exists purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or even be removed.
|
||||||
|
//
|
||||||
|
// We mean it.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <QtCore/qjnienvironment.h>
|
||||||
|
#include <QtCore/qjnitypes.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
Q_DECLARE_JNI_CLASS(View, "android/view/View");
|
||||||
|
|
||||||
|
namespace QtAndroidWindowEmbedding
|
||||||
|
{
|
||||||
|
bool registerNatives(QJniEnvironment& env);
|
||||||
|
void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView);
|
||||||
|
Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(createRootWindow)
|
||||||
|
void deleteWindow(JNIEnv *, jclass, jlong window);
|
||||||
|
Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(deleteWindow)
|
||||||
|
void setWindowVisible(JNIEnv *, jclass, jlong window, jboolean visible);
|
||||||
|
Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setWindowVisible)
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // QTANDROIDWINDOWEMBEDDING_H
|
Loading…
x
Reference in New Issue
Block a user