1 | // Copyright (C) 2016 BlackBerry Limited, Copyright (C) 2016 BasysKom GmbH |
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 "qnearfieldtarget_neard_p.h" |
5 | |
6 | #include <qndefnfctextrecord.h> |
7 | #include <qndefnfcsmartposterrecord.h> |
8 | #include <qndefnfcurirecord.h> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | Q_DECLARE_LOGGING_CATEGORY(QT_NFC_NEARD) |
13 | |
14 | QNearFieldTargetPrivateImpl::QNearFieldTargetPrivateImpl(QObject *parent, QDBusObjectPath interfacePath) |
15 | : QNearFieldTargetPrivate(parent), |
16 | m_tagPath(interfacePath), |
17 | m_readRequested(false) |
18 | { |
19 | m_readErrorTimer.setSingleShot(true); |
20 | m_recordPathsCollectedTimer.setSingleShot(true); |
21 | m_delayedWriteTimer.setSingleShot(true); |
22 | |
23 | qCDebug(QT_NFC_NEARD) << "tag found at path" << interfacePath.path(); |
24 | m_dbusProperties = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.neard" ), |
25 | interfacePath.path(), QDBusConnection::systemBus(), this); |
26 | if (!m_dbusProperties->isValid()) { |
27 | qCWarning(QT_NFC_NEARD) << "Could not connect to dbus property interface at path" |
28 | << interfacePath.path(); |
29 | return; |
30 | } |
31 | |
32 | QDBusPendingReply<QVariantMap> reply = m_dbusProperties->GetAll(QStringLiteral("org.neard.Tag" )); |
33 | reply.waitForFinished(); |
34 | if (reply.isError()) { |
35 | qCWarning(QT_NFC_NEARD) << "Could not get properties of org.neard.Tag dbus interface" ; |
36 | return; |
37 | } |
38 | |
39 | const QString &type = reply.value().value(QStringLiteral("Type" )).toString(); |
40 | m_type = QNearFieldTarget::ProprietaryTag; |
41 | |
42 | if (type == QStringLiteral("Type 1" )) |
43 | m_type = QNearFieldTarget::NfcTagType1; |
44 | else if (type == QStringLiteral("Type 2" )) |
45 | m_type = QNearFieldTarget::NfcTagType2; |
46 | else if (type == QStringLiteral("Type 3" )) |
47 | m_type = QNearFieldTarget::NfcTagType3; |
48 | else if (type == QStringLiteral("Type 4" )) |
49 | m_type = QNearFieldTarget::NfcTagType4; |
50 | |
51 | qCDebug(QT_NFC_NEARD) << "tag type" << type; |
52 | |
53 | connect(&m_recordPathsCollectedTimer, &QTimer::timeout, |
54 | this, &QNearFieldTargetPrivateImpl::createNdefMessage); |
55 | connect(&m_readErrorTimer, &QTimer::timeout, |
56 | this, &QNearFieldTargetPrivateImpl::handleReadError); |
57 | connect(&m_delayedWriteTimer, &QTimer::timeout, |
58 | this, &QNearFieldTargetPrivateImpl::handleWriteRequest); |
59 | connect(NeardHelper::instance(), &NeardHelper::recordFound, |
60 | this, &QNearFieldTargetPrivateImpl::handleRecordFound); |
61 | } |
62 | |
63 | QNearFieldTargetPrivateImpl::~QNearFieldTargetPrivateImpl() |
64 | { |
65 | } |
66 | |
67 | bool QNearFieldTargetPrivateImpl::isValid() |
68 | { |
69 | return m_dbusProperties->isValid() && NeardHelper::instance()->dbusObjectManager()->isValid(); |
70 | } |
71 | |
72 | QByteArray QNearFieldTargetPrivateImpl::uid() const |
73 | { |
74 | return QByteArray(); // TODO figure out a workaround because neard does not offer |
75 | // this property |
76 | } |
77 | |
78 | QNearFieldTarget::Type QNearFieldTargetPrivateImpl::type() const |
79 | { |
80 | return m_type; |
81 | } |
82 | |
83 | QNearFieldTarget::AccessMethods QNearFieldTargetPrivateImpl::accessMethods() const |
84 | { |
85 | return QNearFieldTarget::NdefAccess; |
86 | } |
87 | |
88 | bool QNearFieldTargetPrivateImpl::hasNdefMessage() |
89 | { |
90 | return !m_recordPaths.isEmpty(); |
91 | } |
92 | |
93 | QNearFieldTarget::RequestId QNearFieldTargetPrivateImpl::readNdefMessages() |
94 | { |
95 | if (isValid()) { |
96 | // if the user calls readNdefMessages before the previous request has been completed |
97 | // return the current request id. |
98 | if (m_currentReadRequestId.isValid()) |
99 | return m_currentReadRequestId; |
100 | |
101 | QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate()); |
102 | // save the id so it can be passed along with requestCompleted |
103 | m_currentReadRequestId = requestId; |
104 | // since the triggering of interfaceAdded will ultimately lead to createNdefMessage being called |
105 | // we need to make sure that ndefMessagesRead will only be triggered when readNdefMessages has |
106 | // been called before. In case readNdefMessages is called again after that we can directly call |
107 | // call createNdefMessage. |
108 | m_readRequested = true; |
109 | if (hasNdefMessage()) |
110 | createNdefMessage(); |
111 | else |
112 | m_readErrorTimer.start(1000); |
113 | |
114 | return requestId; |
115 | } else { |
116 | return QNearFieldTarget::RequestId(); |
117 | } |
118 | } |
119 | |
120 | QNearFieldTarget::RequestId QNearFieldTargetPrivateImpl::sendCommand(const QByteArray &command) |
121 | { |
122 | Q_UNUSED(command); |
123 | return QNearFieldTarget::RequestId(); |
124 | } |
125 | |
126 | QNearFieldTarget::RequestId QNearFieldTargetPrivateImpl::writeNdefMessages(const QList<QNdefMessage> &messages) |
127 | { |
128 | // disabling write due to neard crash (see QTBUG-43802) |
129 | qWarning(msg: "QNearFieldTarget::WriteNdefMessages() disabled. See QTBUG-43802\n" ); |
130 | return QNearFieldTarget::RequestId(); |
131 | |
132 | |
133 | // return old request id when previous write request hasn't completed |
134 | if (m_currentWriteRequestId.isValid()) |
135 | return m_currentWriteRequestId; |
136 | |
137 | qCDebug(QT_NFC_NEARD) << "writing messages" ; |
138 | if (messages.isEmpty() || messages.first().isEmpty()) { |
139 | qCWarning(QT_NFC_NEARD) << "No record specified" ; |
140 | return QNearFieldTarget::RequestId(); |
141 | } |
142 | if (messages.count() > 1 || messages.first().count() > 1) { |
143 | // neard only supports one ndef record per tag |
144 | qCWarning(QT_NFC_NEARD) << "Writing of only one NDEF record and message is supported" ; |
145 | return QNearFieldTarget::RequestId(); |
146 | } |
147 | QNdefRecord record = messages.first().first(); |
148 | |
149 | if (record.typeNameFormat() == QNdefRecord::NfcRtd) { |
150 | m_currentWriteRequestData.clear(); |
151 | if (record.isRecordType<QNdefNfcUriRecord>()) { |
152 | m_currentWriteRequestData.insert(QStringLiteral("Type" ), QStringLiteral("URI" )); |
153 | QNdefNfcUriRecord uriRecord = static_cast<QNdefNfcUriRecord>(record); |
154 | m_currentWriteRequestData.insert(QStringLiteral("URI" ), uriRecord.uri().toString()); |
155 | } else if (record.isRecordType<QNdefNfcSmartPosterRecord>()) { |
156 | m_currentWriteRequestData.insert(QStringLiteral("Type" ), QStringLiteral("SmartPoster" )); |
157 | QNdefNfcSmartPosterRecord spRecord = static_cast<QNdefNfcSmartPosterRecord>(record); |
158 | m_currentWriteRequestData.insert(QStringLiteral("URI" ), spRecord.uri().toString()); |
159 | // Currently neard only supports the uri property for writing |
160 | } else if (record.isRecordType<QNdefNfcTextRecord>()) { |
161 | m_currentWriteRequestData.insert(QStringLiteral("Type" ), QStringLiteral("Text" )); |
162 | QNdefNfcTextRecord textRecord = static_cast<QNdefNfcTextRecord>(record); |
163 | m_currentWriteRequestData.insert(QStringLiteral("Representation" ), value: textRecord.text()); |
164 | m_currentWriteRequestData.insert(QStringLiteral("Encoding" ), |
165 | value: textRecord.encoding() == QNdefNfcTextRecord::Utf8 ? |
166 | QStringLiteral("UTF-8" ) : QStringLiteral("UTF-16" ) ); |
167 | m_currentWriteRequestData.insert(QStringLiteral("Language" ), value: textRecord.locale()); |
168 | } else { |
169 | qCWarning(QT_NFC_NEARD) << "Record type not supported for writing" ; |
170 | return QNearFieldTarget::RequestId(); |
171 | } |
172 | |
173 | m_currentWriteRequestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate()); |
174 | // trigger delayed write |
175 | m_delayedWriteTimer.start(100); |
176 | |
177 | return m_currentWriteRequestId; |
178 | } |
179 | |
180 | return QNearFieldTarget::RequestId(); |
181 | } |
182 | |
183 | QNdefRecord QNearFieldTargetPrivateImpl::readRecord(const QDBusObjectPath &path) |
184 | { |
185 | qCDebug(QT_NFC_NEARD) << "reading record for path" << path.path(); |
186 | OrgFreedesktopDBusPropertiesInterface recordInterface(QStringLiteral("org.neard" ), path.path(), |
187 | QDBusConnection::systemBus()); |
188 | if (!recordInterface.isValid()) |
189 | return QNdefRecord(); |
190 | |
191 | QDBusPendingReply<QVariantMap> reply = recordInterface.GetAll(QStringLiteral("org.neard.Record" )); |
192 | reply.waitForFinished(); |
193 | if (reply.isError()) |
194 | return QNdefRecord(); |
195 | |
196 | const QString &value = reply.value().value(QStringLiteral("Representation" )).toString(); |
197 | const QString &locale = reply.value().value(QStringLiteral("Language" )).toString(); |
198 | const QString &encoding = reply.value().value(QStringLiteral("Encoding" )).toString(); |
199 | const QString &uri = reply.value().value(QStringLiteral("URI" )).toString(); |
200 | |
201 | //const QString &mime = reply.value().value(QStringLiteral("MIME")).toString(); |
202 | //const QString &arr = reply.value().value(QStringLiteral("ARR")).toString(); |
203 | |
204 | const QString type = reply.value().value(QStringLiteral("Type" )).toString(); |
205 | if (type == QStringLiteral("Text" )) { |
206 | QNdefNfcTextRecord textRecord; |
207 | textRecord.setText(value); |
208 | textRecord.setLocale(locale); |
209 | textRecord.setEncoding((encoding == QStringLiteral("UTF-8" )) ? QNdefNfcTextRecord::Utf8 |
210 | : QNdefNfcTextRecord::Utf16); |
211 | return textRecord; |
212 | } else if (type == QStringLiteral("SmartPoster" )) { |
213 | QNdefNfcSmartPosterRecord spRecord; |
214 | if (!value.isEmpty()) { |
215 | spRecord.addTitle(text: value, locale, encoding: (encoding == QStringLiteral("UTF-8" )) |
216 | ? QNdefNfcTextRecord::Utf8 |
217 | : QNdefNfcTextRecord::Utf16); |
218 | } |
219 | |
220 | if (!uri.isEmpty()) |
221 | spRecord.setUri(QUrl(uri)); |
222 | |
223 | const QString &action = reply.value().value(QStringLiteral("Action" )).toString(); |
224 | if (!action.isEmpty()) { |
225 | if (action == QStringLiteral("Do" )) |
226 | spRecord.setAction(QNdefNfcSmartPosterRecord::DoAction); |
227 | else if (action == QStringLiteral("Save" )) |
228 | spRecord.setAction(QNdefNfcSmartPosterRecord::SaveAction); |
229 | else if (action == QStringLiteral("Edit" )) |
230 | spRecord.setAction(QNdefNfcSmartPosterRecord::EditAction); |
231 | } |
232 | |
233 | if (reply.value().contains(QStringLiteral("Size" ))) { |
234 | uint size = reply.value().value(QStringLiteral("Size" )).toUInt(); |
235 | spRecord.setSize(size); |
236 | } |
237 | |
238 | const QString &mimeType = reply.value().value(QStringLiteral("MIMEType" )).toString(); |
239 | if (!mimeType.isEmpty()) { |
240 | spRecord.setTypeInfo(mimeType); |
241 | } |
242 | |
243 | |
244 | return spRecord; |
245 | } else if (type == QStringLiteral("URI" )) { |
246 | QNdefNfcUriRecord uriRecord; |
247 | uriRecord.setUri(QUrl(uri)); |
248 | return uriRecord; |
249 | } else if (type == QStringLiteral("MIME" )) { |
250 | |
251 | } else if (type == QStringLiteral("AAR" )) { |
252 | |
253 | } |
254 | |
255 | return QNdefRecord(); |
256 | } |
257 | |
258 | void QNearFieldTargetPrivateImpl::handleRecordFound(const QDBusObjectPath &path) |
259 | { |
260 | m_recordPaths.append(t: path); |
261 | // FIXME: this timer only exists because neard doesn't currently supply enough |
262 | // information to let us know when all record interfaces have been added or |
263 | // how many records are actually contained on a tag. We assume that when no |
264 | // signal has been received for 100ms all record interfaces have been added. |
265 | m_recordPathsCollectedTimer.start(100); |
266 | // as soon as record paths have been added we can handle errors without the timer. |
267 | m_readErrorTimer.stop(); |
268 | } |
269 | |
270 | void QNearFieldTargetPrivateImpl::createNdefMessage() |
271 | { |
272 | if (m_readRequested) { |
273 | qCDebug(QT_NFC_NEARD) << "creating Ndef message, reading" << m_recordPaths.length() << "record paths" ; |
274 | QNdefMessage newNdefMessage; |
275 | for (const QDBusObjectPath &recordPath : std::as_const(t&: m_recordPaths)) |
276 | newNdefMessage.append(t: readRecord(path: recordPath)); |
277 | |
278 | if (!newNdefMessage.isEmpty()) { |
279 | QMetaObject::invokeMethod(this, "ndefMessageRead" , Qt::QueuedConnection, |
280 | Q_ARG(QNdefMessage, newNdefMessage)); |
281 | // the request id in requestCompleted has to match the one created in readNdefMessages |
282 | QMetaObject::invokeMethod(this, [this]() { |
283 | Q_EMIT requestCompleted(m_currentReadRequestId); |
284 | }, Qt::QueuedConnection); |
285 | } else { |
286 | reportError(QNearFieldTarget::UnknownError, m_currentReadRequestId); |
287 | } |
288 | |
289 | m_readRequested = false; |
290 | // invalidate the current request id |
291 | m_currentReadRequestId = QNearFieldTarget::RequestId(0); |
292 | } |
293 | } |
294 | |
295 | void QNearFieldTargetPrivateImpl::handleReadError() |
296 | { |
297 | reportError(QNearFieldTarget::UnknownError, m_currentReadRequestId); |
298 | m_currentReadRequestId = QNearFieldTarget::RequestId(0); |
299 | } |
300 | |
301 | void QNearFieldTargetPrivateImpl::handleWriteRequest() |
302 | { |
303 | OrgNeardTagInterface tagInterface(QStringLiteral("org.neard" ), m_tagPath.path(), |
304 | QDBusConnection::systemBus()); |
305 | if (!tagInterface.isValid()) { |
306 | qCWarning(QT_NFC_NEARD) << "tag interface invalid" ; |
307 | } else { |
308 | QDBusPendingReply<> reply; |
309 | reply = tagInterface.Write(m_currentWriteRequestData); |
310 | reply.waitForFinished(); |
311 | if (reply.isError()) { |
312 | qCWarning(QT_NFC_NEARD) << "Error writing to NFC tag" << reply.error(); |
313 | reportError(QNearFieldTarget::UnknownError, m_currentWriteRequestId); |
314 | } |
315 | |
316 | QMetaObject::invokeMethod(this, [this]() { |
317 | Q_EMIT requestCompleted(m_currentWriteRequestId); |
318 | }, Qt::QueuedConnection); |
319 | } |
320 | |
321 | // invalidate current write request |
322 | m_currentWriteRequestId = QNearFieldTarget::RequestId(0); |
323 | } |
324 | |
325 | QT_END_NAMESPACE |
326 | |