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