summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/jar/CMakeLists.txt3
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java36
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java37
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java48
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtView.java130
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtWindow.java3
-rw-r--r--src/plugins/platforms/android/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/android/androidjnimain.cpp33
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.cpp61
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.h38
10 files changed, 376 insertions, 14 deletions
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt
index af47cd779f..698853588c 100644
--- a/src/android/jar/CMakeLists.txt
+++ b/src/android/jar/CMakeLists.txt
@@ -35,6 +35,9 @@ set(java_sources
src/org/qtproject/qt/android/QtWindow.java
src/org/qtproject/qt/android/QtActivityDelegateBase.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
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java
index f8438003e1..cab542622b 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java
@@ -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
package org.qtproject.qt.android;
@@ -22,8 +22,14 @@ import java.util.ArrayList;
import java.util.HashMap;
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 static native void createRootWindow(View rootView);
+ static native void deleteWindow(long windowReference);
+
public QtEmbeddedDelegate(Activity context) {
super(context);
@@ -71,6 +77,7 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
if (m_activity == activity && m_stateDetails.isStarted) {
m_activity.getApplication().unregisterActivityLifecycleCallbacks(this);
QtNative.unregisterAppStateListener(QtEmbeddedDelegate.this);
+ QtEmbeddedDelegateFactory.remove(m_activity);
QtNative.terminateQt();
QtNative.setActivity(null);
QtNative.getQtThread().exit();
@@ -89,6 +96,8 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
QtDisplayManager.setApplicationDisplayMetrics(m_activity,
metrics.widthPixels,
metrics.heightPixels);
+ if (m_view != null)
+ createRootWindow(m_view);
});
}
}
@@ -111,11 +120,30 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS
@Override
QtLayout getQtLayout()
{
- // TODO could probably use QtView here when it's added?
- return null;
+ // TODO verify if returning m_view here works, this is used by the androidjniinput
+ // 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() {
- // TODO delete the window once it's added
+ if (m_rootWindowRef != 0L)
+ deleteWindow(m_rootWindowRef);
+ m_rootWindowRef = 0L;
}
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java
new file mode 100644
index 0000000000..8cf89e5bc3
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java
@@ -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);
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java
new file mode 100644
index 0000000000..6c3cbb3bfb
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java
@@ -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());
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtView.java b/src/android/jar/src/org/qtproject/qt/android/QtView.java
new file mode 100644
index 0000000000..a140bfa606
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtView.java
@@ -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;
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java
index da744a8d01..4f27532c0d 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java
@@ -140,7 +140,8 @@ public class QtWindow implements QtSurfaceInterface, QtLayout.QtTouchListener {
QtNative.runAction(new Runnable() {
@Override
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));
}
});
}
diff --git a/src/plugins/platforms/android/CMakeLists.txt b/src/plugins/platforms/android/CMakeLists.txt
index a3344bfd71..d5a275a76c 100644
--- a/src/plugins/platforms/android/CMakeLists.txt
+++ b/src/plugins/platforms/android/CMakeLists.txt
@@ -40,6 +40,7 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
qandroidplatformtheme.cpp qandroidplatformtheme.h
qandroidplatformwindow.cpp qandroidplatformwindow.h
qandroidsystemlocale.cpp qandroidsystemlocale.h
+ androidwindowembedding.cpp androidwindowembedding.h
NO_UNITY_BUILD_SOURCES
# Conflicting symbols and macros with androidjnimain.cpp
# TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar
diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp
index bf15213b66..206bbd03d6 100644
--- a/src/plugins/platforms/android/androidjnimain.cpp
+++ b/src/plugins/platforms/android/androidjnimain.cpp
@@ -13,6 +13,7 @@
#include "androidjniinput.h"
#include "androidjnimain.h"
#include "androidjnimenu.h"
+#include "androidwindowembedding.h"
#include "qandroidassetsfileenginehandler.h"
#include "qandroideventdispatcher.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_DECLARE_JNI_CLASS(QtEmbeddedDelegateFactory, "org/qtproject/qt/android/QtEmbeddedDelegateFactory")
+
namespace QtAndroid
{
QBasicMutex *platformInterfaceMutex()
@@ -187,10 +190,17 @@ namespace QtAndroid
// FIXME: avoid direct access to QtActivityDelegate
QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
{
+ using namespace QtJniTypes;
if (!m_activityDelegate.isValid()) {
- auto activity = QtAndroidPrivate::activity();
- m_activityDelegate = activity.callMethod<QtJniTypes::QtActivityDelegateBase>(
- "getActivityDelegate");
+ if (isQtApplication()) {
+ auto context = QtAndroidPrivate::activity();
+ m_activityDelegate = context.callMethod<QtActivityDelegateBase>("getActivityDelegate");
+ } else {
+ m_activityDelegate = QJniObject::callStaticMethod<QtActivityDelegateBase>(
+ Traits<QtEmbeddedDelegateFactory>::className(),
+ "getActivityDelegate",
+ QtAndroidPrivate::activity());
+ }
}
return m_activityDelegate;
@@ -213,11 +223,15 @@ namespace QtAndroid
// 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.
JNIEnv *env = QJniEnvironment::getJniEnv();
- static const jint isQtActivity = env->IsInstanceOf(QtAndroidPrivate::activity().object(),
- m_qtActivityClass);
- static const jint isQtService = env->IsInstanceOf(QtAndroidPrivate::service().object(),
- m_qtServiceClass);
- return isQtActivity || isQtService;
+ auto activity = QtAndroidPrivate::activity();
+ if (activity.isValid())
+ return env->IsInstanceOf(activity.object(), m_qtActivityClass);
+ auto service = QtAndroidPrivate::service();
+ 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)
@@ -870,7 +884,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
|| !QtAndroidAccessibility::registerNatives(env)
|| !QtAndroidDialogHelpers::registerNatives(env)
|| !QAndroidPlatformClipboard::registerNatives(env)
- || !QAndroidPlatformWindow::registerNatives(env)) {
+ || !QAndroidPlatformWindow::registerNatives(env)
+ || !QtAndroidWindowEmbedding::registerNatives(env)) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
}
diff --git a/src/plugins/platforms/android/androidwindowembedding.cpp b/src/plugins/platforms/android/androidwindowembedding.cpp
new file mode 100644
index 0000000000..7d82ac6d87
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.cpp
@@ -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
diff --git a/src/plugins/platforms/android/androidwindowembedding.h b/src/plugins/platforms/android/androidwindowembedding.h
new file mode 100644
index 0000000000..f9d92d4afc
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.h
@@ -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