Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidquickviewembedding.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/private/qandroidtypeconverter_p.h>
5#include <QtCore/private/qandroidtypes_p.h>
6#include <QtQuick/private/qandroidquickviewembedding_p.h>
7#include <QtQuick/private/qandroidviewsignalmanager_p.h>
8#include <QtCore/private/qmetaobject_p.h>
9#include <QtCore/qmetatype.h>
10
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qjnienvironment.h>
13#include <QtCore/qjniobject.h>
14#include <QtCore/qjniarray.h>
15#include <QtCore/qjnitypes.h>
16#include <QtQml/qqmlengine.h>
17#include <QtQuick/qquickitem.h>
18#include <functional>
19#include <jni.h>
20#include <memory>
21
23
24Q_DECLARE_JNI_CLASS(QtDelegate, "org/qtproject/qt/android/QtEmbeddedContextDelegate");
25Q_DECLARE_JNI_CLASS(QtQuickView, "org/qtproject/qt/android/QtQuickView");
26Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow");
27Q_DECLARE_JNI_CLASS(View, "android/view/View");
28
30{
31 constexpr const char *uninitializedViewMessage = "because QtQuickView is not loaded or ready yet.";
32
33 static void onQQuickViewStatusChanged(const QJniObject &qtViewObject,
34 QAndroidQuickView::Status status)
35 {
36 auto future = QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
37 [qtViewObject, status] {
38 qtViewObject.callMethod<void>("handleStatusChange", status);
39 }, QDeadlineTimer(1000));
40 future.waitForFinished(); // Wait for the user to handle status change.
41 }
42
43 void createQuickView(JNIEnv *, jobject nativeWindow, jstring qmlUri, jint width, jint height,
44 jlong parentWindowReference, jlong viewReference,
45 const QJniArray<jstring> &qmlImportPaths)
46 {
47 static_assert (sizeof(jlong) >= sizeof(void*),
48 "Insufficient size of Java type to hold the c++ pointer");
49 const QUrl qmlUrl(QJniObject(qmlUri).toString());
50
51 const QStringList importPaths = qmlImportPaths.toContainer();
52 QMetaObject::invokeMethod(qApp, [qtViewObject = QJniObject(nativeWindow),
53 parentWindowReference,
54 viewReference,
55 width,
56 height,
57 qmlUrl,
58 importPaths] {
59 // If the view does not exists (viewReference==0) we should create and set it up.
60 // Else we only reset the source of the view.
61 QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(viewReference);
62 if (!view) {
63 QWindow *parentWindow = reinterpret_cast<QWindow *>(parentWindowReference);
64 view = new QAndroidQuickView(parentWindow);
65 QObject::connect(
66 view, &QAndroidQuickView::statusChanged,
67 std::bind(&onQQuickViewStatusChanged, qtViewObject, std::placeholders::_1));
68 view->setResizeMode(QAndroidQuickView::SizeRootObjectToView);
69 view->setColor(QColor(Qt::transparent));
70 view->setWidth(width);
71 view->setHeight(height);
72 QQmlEngine *engine = view->engine();
73 for (const QString &path : importPaths)
74 engine->addImportPath(path);
75
76 QObject::connect(engine, &QQmlEngine::quit, QCoreApplication::instance(),
77 &QCoreApplication::quit);
78
79 const QtJniTypes::QtWindow window = reinterpret_cast<jobject>(view->winId());
80 qtViewObject.callMethod<void>("addQtWindow",
81 window,
82 reinterpret_cast<jlong>(view),
83 parentWindowReference);
84 }
85 view->setSource(qmlUrl);
86 });
87 }
88
90 {
91 QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(windowReference);
92 QQuickItem *rootObject = Q_LIKELY(view) ? view->rootObject() : nullptr;
93 return std::make_pair(view, rootObject);
94 }
95
96 void setRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
97 jstring propertyName, jobject value)
98 {
99 Q_UNUSED(env);
100 Q_UNUSED(object);
101
102 auto [_, rootObject] = getViewAndRootObject(windowReference);
103 if (!rootObject) {
104 qWarning("Cannot set property %s %s", qPrintable(QJniObject(propertyName).toString()),
106 return;
107 }
108
109 const QString property = QJniObject(propertyName).toString();
110 const QMetaObject *rootMetaObject = rootObject->metaObject();
111 int propertyIndex = rootMetaObject->indexOfProperty(qPrintable(property));
112 if (propertyIndex < 0) {
113 qWarning("Property %s does not exist in the root QML object.", qPrintable(property));
114 return;
115 }
116
117 const QJniObject propertyValue(value);
118 const QVariant variantToWrite = QAndroidTypeConverter::toQVariant(propertyValue);
119
120 if (!variantToWrite.isValid()) {
121 qWarning("Setting the property type of %s is not supported.",
122 propertyValue.className().data());
123 } else {
124 QMetaObject::invokeMethod(rootObject,
125 [metaProperty = rootMetaObject->property(propertyIndex),
126 rootObject = rootObject,
127 variantToWrite] {
128 metaProperty.write(rootObject, variantToWrite);
129 });
130 }
131 }
132
133 jobject getRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
134 jstring propertyName)
135 {
136 Q_UNUSED(object);
137 Q_ASSERT(env);
138
139 const QString property = QJniObject(propertyName).toString();
140 auto [_, rootObject] = getViewAndRootObject(windowReference);
141 if (!rootObject) {
142 qWarning("Cannot get property %s %s", qPrintable(property), uninitializedViewMessage);
143 return nullptr;
144 }
145
146 const QMetaObject *rootMetaObject = rootObject->metaObject();
147 int propertyIndex = rootMetaObject->indexOfProperty(property.toUtf8().constData());
148 if (propertyIndex < 0) {
149 qWarning("Cannot get property %s as it does not exist in the root QML object.",
150 qPrintable(property));
151 return nullptr;
152 }
153
154 QMetaProperty metaProperty = rootMetaObject->property(propertyIndex);
155 QVariant propertyValue;
156 if (QCoreApplication::instance()->thread()->isCurrentThread()) {
157 propertyValue = metaProperty.read(rootObject);
158 } else {
159 QMetaObject::invokeMethod(rootObject,
160 [&propertyValue, &metaProperty, rootObject = rootObject] {
161 propertyValue = metaProperty.read(rootObject);
162 }, Qt::BlockingQueuedConnection);
163 }
164 jobject jObject = QAndroidTypeConverter::toJavaObject(propertyValue, env);
165 if (!jObject) {
166 qWarning("Property %s cannot be converted to a supported Java data type.",
167 qPrintable(property));
168 }
169 return jObject;
170 }
171
172 bool addRootObjectSignalListener(JNIEnv *env, jobject, jlong windowReference,
173 jstring signalName, QJniArray<jclass> argTypes,
174 jobject listener, jint id)
175 {
176 Q_ASSERT(env);
177
178 auto [view, _] = getViewAndRootObject(windowReference);
179 if (!view) {
180 qWarning("Cannot connect to signal %s %s",
181 qPrintable(QJniObject(signalName).toString()), uninitializedViewMessage);
182 return false;
183 }
184
185 QAndroidViewSignalManager *signalManager = view->signalManager();
186 return signalManager->addConnection(QJniObject(signalName).toString(), argTypes,
187 QJniObject(listener), id);
188 }
189
190 bool removeRootObjectSignalListener(JNIEnv *, jobject, jlong windowReference,
191 jint signalListenerId)
192 {
193 auto [view, rootObject] = getViewAndRootObject(windowReference);
194 if (!rootObject) {
195 qWarning("Cannot disconnect the signal connection with id: %i %s", signalListenerId,
197 return false;
198 }
199 view->signalManager()->removeConnection(signalListenerId);
200 return true;
201 }
202
204 {
205 switch (type) {
206 case QMetaType::Bool:
207 return QVariant::fromValue(
208 QtJniTypes::Boolean::construct(obj).callMethod<bool>("booleanValue"));
209 break;
210 case QMetaType::Int:
211 return QVariant::fromValue(
212 QtJniTypes::Integer::construct(obj).callMethod<int>("intValue"));
213 break;
214 case QMetaType::Double:
215 return QVariant::fromValue(
216 QtJniTypes::Double::construct(obj).callMethod<double>("doubleValue"));
217 break;
218 case QMetaType::Float:
219 return QVariant::fromValue(
220 QtJniTypes::Float::construct(obj).callMethod<float>("floatValue"));
221 break;
222 case QMetaType::QString:
224 break;
225 default:
226 qWarning("Unsupported metatype: %s", QMetaType(type).name());
227 return QVariant();
228 }
229 }
230
231 QMetaMethod findMethod(const QString &name, int paramCount, const QMetaObject &object)
232 {
233 for (auto i = object.methodOffset(); i < object.methodCount(); ++i) {
234 QMetaMethod method = object.method(i);
235 const auto paramMatch = method.parameterCount() == paramCount;
236 const auto nameMatch = method.name() == name.toUtf8();
237 if (paramMatch && nameMatch)
238 return method;
239 }
240 return QMetaMethod();
241 }
242
243 void invokeMethod(JNIEnv *, jobject, jlong viewReference, QtJniTypes::String methodName,
244 QJniArray<jobject> jniParams)
245 {
246 auto [_, rootObject] = getViewAndRootObject(viewReference);
247 if (!rootObject) {
248 qWarning() << "Cannot invoke QML method" << methodName.toString()
249 << "as the QML view has not been loaded yet.";
250 return;
251 }
252
253 const auto paramCount = jniParams.size();
254 QMetaMethod method =
255 findMethod(methodName.toString(), paramCount, *rootObject->metaObject());
256 if (!method.isValid()) {
257 qWarning() << "Failed to find method" << QJniObject(methodName).toString()
258 << "in QQuickView";
259 return;
260 }
261
262 // Invoke and leave early if there are no params to pass on
263 if (paramCount == 0) {
264 method.invoke(rootObject, Qt::QueuedConnection);
265 return;
266 }
267
268 QList<QVariant> variants;
269 variants.reserve(jniParams.size());
270 variants.emplace_back(QVariant{}); // "Data" for the return value
271
272 for (auto i = 0; i < paramCount; ++i) {
273 const auto type = method.parameterType(i);
274 if (type == QMetaType::UnknownType) {
275 qWarning("Unknown metatypes are not supported.");
276 return;
277 }
278
279 jobject rawParam = jniParams.at(i);
280 auto variant = variants.emplace_back(
281 jobjectToVariant(static_cast<QMetaType::Type>(type), rawParam));
282 if (variant.isNull()) {
283 auto className = QJniObject(rawParam).className();
284 qWarning("Failed to convert param with class name '%s' to QVariant",
285 className.constData());
286 return;
287 }
288 }
289
290 // Initialize the data arrays for params, typenames and type conversion interfaces.
291 // Note that this is adding an element, this is for the return value which is at idx 0.
292 const int paramsCount = method.parameterCount() + 1;
293 const auto paramTypes = std::make_unique<const char *[]>(paramsCount);
294 const auto params = std::make_unique<const void *[]>(paramsCount);
295 const auto metaTypes =
296 std::make_unique<const QtPrivate::QMetaTypeInterface *[]>(paramsCount);
297
298 // We're not expecting a return value, so index 0 can be all nulls.
299 paramTypes[0] = nullptr;
300 params[0] = nullptr;
301 metaTypes[0] = nullptr;
302
303 for (auto i = 1; i < variants.size(); ++i) {
304 const auto &variant = variants.at(i);
305 paramTypes[i] = variant.typeName();
306 params[i] = variant.data();
307 metaTypes[i] = variant.metaType().iface();
308 }
309
310 auto reason = QMetaMethodInvoker::invokeImpl(method,
311 rootObject,
312 Qt::QueuedConnection,
313 paramsCount,
314 params.get(),
315 paramTypes.get(),
316 metaTypes.get());
317
318 if (reason != QMetaMethodInvoker::InvokeFailReason::None)
319 qWarning() << "Failed to invoke function" << methodName.toString()
320 << ", Reason:" << int(reason);
321 }
322
323 bool registerNatives(QJniEnvironment& env) {
324 return env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtQuickView>::className(),
325 {Q_JNI_NATIVE_SCOPED_METHOD(createQuickView,
326 QtAndroidQuickViewEmbedding),
327 Q_JNI_NATIVE_SCOPED_METHOD(setRootObjectProperty,
328 QtAndroidQuickViewEmbedding),
329 Q_JNI_NATIVE_SCOPED_METHOD(getRootObjectProperty,
330 QtAndroidQuickViewEmbedding),
331 Q_JNI_NATIVE_SCOPED_METHOD(addRootObjectSignalListener,
332 QtAndroidQuickViewEmbedding),
333 Q_JNI_NATIVE_SCOPED_METHOD(removeRootObjectSignalListener,
334 QtAndroidQuickViewEmbedding),
335 Q_JNI_NATIVE_SCOPED_METHOD(invokeMethod,
336 QtAndroidQuickViewEmbedding)});
337 }
338}
339
340Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
341{
342 Q_UNUSED(vm)
343 Q_UNUSED(reserved)
344
345 static bool initialized = false;
346 if (initialized)
347 return JNI_VERSION_1_6;
348 initialized = true;
349
350 QJniEnvironment env;
351 if (!env.isValid())
352 return JNI_ERR;
353 if (!QtAndroidQuickViewEmbedding::registerNatives(env))
354 return JNI_ERR;
355 return JNI_VERSION_1_6;
356}
357
358QT_END_NAMESPACE
std::pair< QAndroidQuickView *, QQuickItem * > getViewAndRootObject(jlong windowReference)
void setRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference, jstring propertyName, jobject value)
bool addRootObjectSignalListener(JNIEnv *env, jobject, jlong windowReference, jstring signalName, QJniArray< jclass > argTypes, jobject listener, jint id)
constexpr const char * uninitializedViewMessage
bool removeRootObjectSignalListener(JNIEnv *, jobject, jlong windowReference, jint signalListenerId)
void invokeMethod(JNIEnv *, jobject, jlong viewReference, QtJniTypes::String methodName, QJniArray< jobject > jniParams)
void createQuickView(JNIEnv *, jobject nativeWindow, jstring qmlUri, jint width, jint height, jlong parentWindowReference, jlong viewReference, const QJniArray< jstring > &qmlImportPaths)
static void onQQuickViewStatusChanged(const QJniObject &qtViewObject, QAndroidQuickView::Status status)
bool registerNatives(QJniEnvironment &env)
jobject getRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference, jstring propertyName)
QVariant jobjectToVariant(QMetaType::Type type, jobject &obj)
QMetaMethod findMethod(const QString &name, int paramCount, const QMetaObject &object)
Q_DECLARE_JNI_CLASS(MotionEvent, "android/view/MotionEvent")