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