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
qandroidviewsignalmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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 "platform/android/qandroidviewsignalmanager_p.h"
6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qmetaobject.h>
8#include <QtQuick/qquickitem.h>
9#include <QtQuick/qquickview.h>
10
11#include <QtCore/private/qandroidtypeconverter_p.h>
12#include <QtQuick/private/qandroidviewsignalmanager_p.h>
13
15
16QAndroidViewSignalManager::QAndroidViewSignalManager(QQuickView *view, QObject *parent)
17 : QObject(parent), m_view(view)
18{
19 connect(m_view, &QQuickView::statusChanged, this,
20 &QAndroidViewSignalManager::onViewStatusChanged);
21}
22
23void QAndroidViewSignalManager::onViewStatusChanged(QQuickView::Status status)
24{
25 if (status == QQuickView::Ready) {
26 QObject::disconnect(m_view, &QQuickView::statusChanged, this,
27 &QAndroidViewSignalManager::onViewStatusChanged);
28 QMutexLocker lock(&m_queueMutex);
29 for (const auto &info : m_queuedConnections)
30 addConnection(info.signalName, info.argTypes, info.listener, info.id);
31 m_queuedConnections.clear();
32 } else if (status == QQuickView::Error) {
33 m_queuedConnections.clear();
34 }
35}
36
37QByteArray qmlTypeArgStringFromJavaTypeArgs(const QJniArray<jclass> &javaArgClasses)
38{
39 static const QHash<QByteArray, QMetaType::Type> javaToQMetaTypeMap = {
40 { "java/lang/Void", QMetaType::Type::Void },
41 { "java/lang/String", QMetaType::Type::QString },
42 { "java/lang/Integer", QMetaType::Type::Int },
43 { "java/lang/Double", QMetaType::Type::Double },
44 { "java/lang/Float", QMetaType::Type::Float },
45 { "java/lang/Boolean", QMetaType::Type::Bool }
46 };
47
48 QList<QByteArray> qmlArgTypes;
49 for (const auto javaArgClass : javaArgClasses) {
50 const auto javaArgClassName = QJniObject(javaArgClass).className();
51 const auto javaArgMetaType =
52 javaToQMetaTypeMap.value(javaArgClassName, QMetaType::Type::UnknownType);
53
54 if (javaArgMetaType == QMetaType::Type::UnknownType) {
55 qWarning() << "Unknown type for signal argument" << javaArgClassName;
56 return "";
57 }
58
59 qmlArgTypes.emplace_back(QMetaType(javaArgMetaType).name());
60 }
61 return qmlArgTypes.join(',');
62}
63
64QByteArray qmlSignalSignature(const QString &signalName, const QJniArray<jclass> &javaArgClasses)
65{
66 const auto qmlArgTypes = qmlTypeArgStringFromJavaTypeArgs(javaArgClasses);
67 return QMetaObject::normalizedSignature(
68 qPrintable(QStringLiteral("%1(%2)").arg(signalName).arg(qmlArgTypes)));
69}
70
71QList<QMetaType::Type> metaMethodArgumentTypes(const QMetaMethod &method)
72{
73 QList<QMetaType::Type> types;
74 for (auto i = 0; i < method.parameterCount(); ++i)
75 types.push_back(static_cast<QMetaType::Type>(method.parameterType(i)));
76 return types;
77}
78
79int indexOfSignal(const QMetaObject &metaObject, const QString &signalName,
80 const QJniArray<jclass> &javaArgClasses)
81{
82 int signalIndex = metaObject.indexOfSignal(qmlSignalSignature(signalName, javaArgClasses));
83 if (signalIndex != -1)
84 return signalIndex;
85
86 // A parameterless signal may be named without its empty argument list.
87 QString signature = signalName;
88 if (!signature.contains(u'('))
89 signature += QStringLiteral("()");
90 signalIndex = metaObject.indexOfSignal(QMetaObject::normalizedSignature(qPrintable(signature)));
91 if (signalIndex != -1)
92 return signalIndex;
93
94 // The generated change listeners name the property, not its notify signal.
95 const int propertyIndex = metaObject.indexOfProperty(qPrintable(signalName));
96 return propertyIndex == -1 ? -1 : metaObject.property(propertyIndex).notifySignalIndex();
97}
98
99std::optional<int> propertyIndexForSignal(const QMetaMethod &signal, const QMetaObject &object)
100{
101 for (int i = 0; i < object.propertyCount(); ++i) {
102 const auto metaProperty = object.property(i);
103 if (signal.methodSignature() == metaProperty.notifySignal().methodSignature())
104 return metaProperty.propertyIndex();
105 }
106 return std::nullopt;
107}
108
109bool QAndroidViewSignalManager::addConnection(const QString &signalName,
110 const QJniArray<jclass> &argTypes,
111 const QJniObject &listener,
112 int id)
113{
114 if (m_view->status() == QQuickView::Error) {
115 qWarning("Can not connect to signals due to errors while loading the view");
116 return false;
117 }
118
119 if (m_view->status() != QQuickView::Ready) {
120 return queueConnection(signalName, argTypes, listener, id);
121 }
122
123 const auto *rootMetaObject = m_view->rootObject()->metaObject();
124 int signalIndex = indexOfSignal(*rootMetaObject, signalName, argTypes);
125 if (signalIndex == -1) {
126 qWarning("Failed to find matching signal from root object for signal: %s",
127 qPrintable(signalName));
128 return false;
129 }
130
131 if (m_connections.contains(signalIndex))
132 return true;
133
134 // Connect the signal to this class' qt_metacall
135 const auto connection =
136 QMetaObject::connect(m_view->rootObject(), signalIndex, this,
137 QObject::metaObject()->methodCount(), Qt::QueuedConnection);
138
139 const auto signal = rootMetaObject->method(signalIndex);
140 const auto propertyIndex = propertyIndexForSignal(signal, *rootMetaObject);
141 const auto argumentTypes = metaMethodArgumentTypes(signal);
142
143 m_connections.insert(signalIndex,
144 { .connection = connection,
145 .listenerObject = listener,
146 .qmlSignalName = signalName,
147 .qmlArgumentTypes = argumentTypes,
148 .isPropertySignal = propertyIndex.has_value(),
149 .qmlPropertyIndex = propertyIndex,
150 .connectionId = id });
151 return true;
152}
153
154bool QAndroidViewSignalManager::queueConnection(const QString &signalName,
155 const QJniArray<jclass> &argTypes,
156 const QJniObject &listener,
157 int id)
158{
159 QMutexLocker lock(&m_queueMutex);
160 m_queuedConnections.push_back({
161 .id = id,
162 .signalName = signalName,
163 .argTypes = argTypes,
164 .listener = listener
165 });
166 return true;
167}
168
169QJniObject voidStarToQJniObject(const QMetaType::Type type, const void *data)
170{
171 switch (type) {
172 case QMetaType::Type::Bool:
173 return QtJniTypes::Boolean::construct(*reinterpret_cast<const bool *>(data));
174 case QMetaType::Type::Int:
175 return QtJniTypes::Integer::construct(*reinterpret_cast<const int *>(data));
176 case QMetaType::Type::Double:
177 return QtJniTypes::Double::construct(*reinterpret_cast<const double *>(data));
178 case QMetaType::Type::Float:
179 return QtJniTypes::Float::construct(*reinterpret_cast<const float *>(data));
180 case QMetaType::Type::QString:
181 return QtJniTypes::String::construct(*reinterpret_cast<const QString *>(data));
182 default:
183 qWarning() << "Unknown type for signal argument" << type;
184 break;
185 }
186 return {};
187}
188
189QJniObject qVariantToQJniObject(const QVariant &data)
190{
191 const auto type = data.metaType().id();
192 switch (type) {
193 case QMetaType::Type::Bool:
194 return QtJniTypes::Boolean::construct(data.toBool());
195 case QMetaType::Type::Int:
196 return QtJniTypes::Integer::construct(data.toInt());
197 case QMetaType::Type::Double:
198 return QtJniTypes::Double::construct(data.toDouble());
199 case QMetaType::Type::Float:
200 return QtJniTypes::Float::construct(data.toFloat());
201 case QMetaType::Type::QString:
202 return QtJniTypes::String::construct(data.toString());
203 default:
204 qWarning() << "Unknown type for signal argument" << type;
205 break;
206 }
207 return {};
208}
209
210int QAndroidViewSignalManager::qt_metacall(QMetaObject::Call call, int methodId, void **args)
211{
212 Q_UNUSED(call);
213 if (!m_connections.contains(senderSignalIndex())) {
214 qWarning() << "QAndroidViewSignalManager::qt_metacall received unexpected signal";
215 return methodId;
216 }
217
218 const auto connection = m_connections.value(senderSignalIndex());
219
220 if (connection.isPropertySignal) {
221 const auto senderMetaObject = sender()->metaObject();
222 const auto property = senderMetaObject->property(connection.qmlPropertyIndex.value());
223 const auto data = property.read(sender());
224
225 connection.listenerObject.callMethod<void>("onSignalEmitted", connection.qmlSignalName,
226 qVariantToQJniObject(data));
227 } else {
228 QJniArray<QJniObject> data(connection.qmlArgumentTypes.size());
229 for (auto i = 0; i < connection.qmlArgumentTypes.size(); ++i) {
230 const auto argType = connection.qmlArgumentTypes.at(i);
231 const auto receivedRawData = args[i + 1];
232
233 if (receivedRawData == nullptr) {
234 qWarning() << "Received null data for signal" << connection.qmlSignalName;
235 return methodId;
236 }
237
238 const auto receivedData = voidStarToQJniObject(argType, receivedRawData);
239 if (!receivedData.isValid()) {
240 qWarning() << "Received invalid data for signal" << connection.qmlSignalName;
241 return methodId;
242 }
243
244 data.setValue(i, receivedData);
245 }
246
247 if (data.size() == 0) { // Signal with no params, uses QtSignalListener<Void>
248 QNativeInterface::QAndroidApplication::runOnAndroidMainThread([connection] {
249 connection.listenerObject.callMethod<void>("onSignalEmitted",
250 connection.qmlSignalName,
251 QtJniTypes::Void::construct().object());
252 });
253 } else if (data.size() == 1) { // Signal with a single param, uses QtSignalListener<T>
254 QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
255 [connection, signalArg = data.at(0)] {
256 connection.listenerObject.callMethod<void>("onSignalEmitted",
257 connection.qmlSignalName,
258 signalArg);
259 });
260 } else { // Signal with multiple params, uses custom listener interface with Object[] API
261 QNativeInterface::QAndroidApplication::runOnAndroidMainThread([connection, data] {
262 connection.listenerObject.callMethod<void>("onSignalEmitted", data);
263 });
264 }
265 }
266 return methodId;
267}
268
269bool QAndroidViewSignalManager::hasConnection(connection_key_t key) const
270{
271 for (const auto &info : m_connections) {
272 if (info.connectionId == key)
273 return true;
274 }
275 return false;
276}
277
278void QAndroidViewSignalManager::removeConnection(connection_key_t key)
279{
280 if (hasConnection(key)) {
281 m_connections.removeIf([key](const auto &iter) {
282 if (iter->connectionId == key)
283 return QObject::disconnect(iter->connection);
284 return false;
285 });
286 } else {
287 QMutexLocker lock(&m_queueMutex);
288 m_queuedConnections.removeIf([key](const auto &info) { return info.id == key; });
289 }
290}
291
292QT_END_NAMESPACE
QObject * parent
Definition qobject.h:74
The QQuickView class provides a window for displaying a Qt Quick user interface.
Definition qquickview.h:21
Combined button and popup list for selecting options.
QJniObject qVariantToQJniObject(const QVariant &data)
std::optional< int > propertyIndexForSignal(const QMetaMethod &signal, const QMetaObject &object)
QByteArray qmlSignalSignature(const QString &signalName, const QJniArray< jclass > &javaArgClasses)
QList< QMetaType::Type > metaMethodArgumentTypes(const QMetaMethod &method)
int indexOfSignal(const QMetaObject &metaObject, const QString &signalName, const QJniArray< jclass > &javaArgClasses)
QByteArray qmlTypeArgStringFromJavaTypeArgs(const QJniArray< jclass > &javaArgClasses)
QJniObject voidStarToQJniObject(const QMetaType::Type type, const void *data)