1/***************************************************************************
2**
3** Copyright (C) 2016 BlackBerry Limited. All rights reserved.
4** Copyright (C) 2016 BasysKom GmbH.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtNfc module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#ifndef QNEARFIELDTARGET_NEARD_P_H
42#define QNEARFIELDTARGET_NEARD_P_H
43
44//
45// W A R N I N G
46// -------------
47//
48// This file is not part of the Qt API. It exists purely as an
49// implementation detail. This header file may change from version to
50// version without notice, or even be removed.
51//
52// We mean it.
53//
54
55#include <QDBusObjectPath>
56#include <QDBusVariant>
57
58#include <qnearfieldtarget.h>
59#include <qnearfieldtarget_p.h>
60#include <qndefrecord.h>
61#include <qndefmessage.h>
62
63#include "neard/neard_helper_p.h"
64#include "neard/dbusproperties_p.h"
65#include "neard/dbusobjectmanager_p.h"
66#include "neard/tag_p.h"
67
68#include <qndefnfctextrecord.h>
69#include <qndefnfcsmartposterrecord.h>
70#include <qndefnfcurirecord.h>
71
72QT_BEGIN_NAMESPACE
73
74Q_DECLARE_LOGGING_CATEGORY(QT_NFC_NEARD)
75
76template <typename T>
77class NearFieldTarget : public T
78{
79public:
80
81 NearFieldTarget(QObject *parent, QDBusObjectPath interfacePath)
82 : T(parent),
83 m_tagPath(interfacePath),
84 m_readRequested(false)
85 {
86 m_readErrorTimer.setSingleShot(true);
87 m_recordPathsCollectedTimer.setSingleShot(true);
88 m_delayedWriteTimer.setSingleShot(true);
89
90 qCDebug(QT_NFC_NEARD) << "tag found at path" << interfacePath.path();
91 m_dbusProperties = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.neard"),
92 interfacePath.path(),
93 QDBusConnection::systemBus(),
94 this);
95 if (!m_dbusProperties->isValid()) {
96 qCWarning(QT_NFC_NEARD) << "Could not connect to dbus property interface at path" << interfacePath.path();
97 return;
98 }
99
100 QDBusPendingReply<QVariantMap> reply = m_dbusProperties->GetAll(QStringLiteral("org.neard.Tag"));
101 reply.waitForFinished();
102 if (reply.isError()) {
103 qCWarning(QT_NFC_NEARD) << "Could not get properties of org.neard.Tag dbus interface";
104 return;
105 }
106
107 const QString &type = reply.value().value(QStringLiteral("Type")).toString();
108 m_type = QNearFieldTarget::ProprietaryTag;
109
110 if (type == QStringLiteral("Type 1"))
111 m_type = QNearFieldTarget::NfcTagType1;
112 else if (type == QStringLiteral("Type 2"))
113 m_type = QNearFieldTarget::NfcTagType2;
114 else if (type == QStringLiteral("Type 3"))
115 m_type = QNearFieldTarget::NfcTagType3;
116 else if (type == QStringLiteral("Type 4"))
117 m_type = QNearFieldTarget::NfcTagType4;
118
119 qCDebug(QT_NFC_NEARD) << "tag type" << type;
120
121 QObject::connect(&m_recordPathsCollectedTimer, &QTimer::timeout,
122 this, &NearFieldTarget::createNdefMessage);
123 QObject::connect(&m_readErrorTimer, &QTimer::timeout,
124 this, &NearFieldTarget::handleReadError);
125 QObject::connect(&m_delayedWriteTimer, &QTimer::timeout,
126 this, &NearFieldTarget::handleWriteRequest);
127 QObject::connect(NeardHelper::instance(), &NeardHelper::recordFound,
128 this, &NearFieldTarget::handleRecordFound);
129 }
130
131 ~NearFieldTarget()
132 {
133 }
134
135 bool isValid()
136 {
137 return m_dbusProperties->isValid() && NeardHelper::instance()->dbusObjectManager()->isValid();
138 }
139
140 QByteArray uid() const
141 {
142 return QByteArray(); // TODO figure out a workaround because neard does not offer
143 // this property
144 }
145
146 QNearFieldTarget::Type type() const
147 {
148 return m_type;
149 }
150
151 QNearFieldTarget::AccessMethods accessMethods() const
152 {
153 return QNearFieldTarget::NdefAccess;
154 }
155
156 bool hasNdefMessage()
157 {
158 return !m_recordPaths.isEmpty();
159 }
160
161 QNearFieldTarget::RequestId readNdefMessages()
162 {
163 if (isValid()) {
164 // if the user calls readNdefMessages before the previous request has been completed
165 // return the current request id.
166 if (m_currentReadRequestId.isValid())
167 return m_currentReadRequestId;
168
169 QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate());
170 // save the id so it can be passed along with requestCompleted
171 m_currentReadRequestId = requestId;
172 // since the triggering of interfaceAdded will ultimately lead to createNdefMessage being called
173 // we need to make sure that ndefMessagesRead will only be triggered when readNdefMessages has
174 // been called before. In case readNdefMessages is called again after that we can directly call
175 // call createNdefMessage.
176 m_readRequested = true;
177 if (hasNdefMessage())
178 createNdefMessage();
179 else
180 m_readErrorTimer.start(msec: 1000);
181
182 return requestId;
183 } else {
184 return QNearFieldTarget::RequestId();
185 }
186 }
187
188 QNearFieldTarget::RequestId sendCommand(const QByteArray &command)
189 {
190 Q_UNUSED(command);
191 return QNearFieldTarget::RequestId();
192 }
193
194 QNearFieldTarget::RequestId sendCommands(const QList<QByteArray> &commands)
195 {
196 Q_UNUSED(commands);
197 return QNearFieldTarget::RequestId();
198 }
199
200 QNearFieldTarget::RequestId writeNdefMessages(const QList<QNdefMessage> &messages)
201 {
202 // disabling write due to neard crash (see QTBUG-43802)
203 qWarning(msg: "QNearFieldTarget::WriteNdefMessages() disabled. See QTBUG-43802\n");
204 return QNearFieldTarget::RequestId();
205
206
207 // return old request id when previous write request hasn't completed
208 if (m_currentWriteRequestId.isValid())
209 return m_currentReadRequestId;
210
211 qCDebug(QT_NFC_NEARD) << "writing messages";
212 if (messages.isEmpty() || messages.first().isEmpty()) {
213 qCWarning(QT_NFC_NEARD) << "No record specified";
214 return QNearFieldTarget::RequestId();
215 }
216 if (messages.count() > 1 || messages.first().count() > 1) {
217 // neard only supports one ndef record per tag
218 qCWarning(QT_NFC_NEARD) << "Writing of only one NDEF record and message is supported";
219 return QNearFieldTarget::RequestId();
220 }
221 QNdefRecord record = messages.first().first();
222
223 if (record.typeNameFormat() == QNdefRecord::NfcRtd) {
224 m_currentWriteRequestData.clear();
225 if (record.isRecordType<QNdefNfcUriRecord>()) {
226 m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("URI"));
227 QNdefNfcUriRecord uriRecord = static_cast<QNdefNfcUriRecord>(record);
228 m_currentWriteRequestData.insert(QStringLiteral("URI"), uriRecord.uri().toString());
229 } else if (record.isRecordType<QNdefNfcSmartPosterRecord>()) {
230 m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("SmartPoster"));
231 QNdefNfcSmartPosterRecord spRecord = static_cast<QNdefNfcSmartPosterRecord>(record);
232 m_currentWriteRequestData.insert(QStringLiteral("URI"), spRecord.uri().toString());
233 // Currently neard only supports the uri property for writing
234 } else if (record.isRecordType<QNdefNfcTextRecord>()) {
235 m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("Text"));
236 QNdefNfcTextRecord textRecord = static_cast<QNdefNfcTextRecord>(record);
237 m_currentWriteRequestData.insert(QStringLiteral("Representation"), textRecord.text());
238 m_currentWriteRequestData.insert(QStringLiteral("Encoding"),
239 textRecord.encoding() == QNdefNfcTextRecord::Utf8 ?
240 QStringLiteral("UTF-8") : QStringLiteral("UTF-16") );
241 m_currentWriteRequestData.insert(QStringLiteral("Language"), textRecord.locale());
242 } else {
243 qCWarning(QT_NFC_NEARD) << "Record type not supported for writing";
244 return QNearFieldTarget::RequestId();
245 }
246
247 m_currentWriteRequestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate());
248 // trigger delayed write
249 m_delayedWriteTimer.start(msec: 100);
250
251 return m_currentWriteRequestId;
252 }
253
254 return QNearFieldTarget::RequestId();
255 }
256
257private:
258 QNdefRecord readRecord(const QDBusObjectPath &path)
259 {
260 qCDebug(QT_NFC_NEARD) << "reading record for path" << path.path();
261 OrgFreedesktopDBusPropertiesInterface recordInterface(QStringLiteral("org.neard"),
262 path.path(),
263 QDBusConnection::systemBus());
264 if (!recordInterface.isValid())
265 return QNdefRecord();
266
267 QDBusPendingReply<QVariantMap> reply = recordInterface.GetAll(QStringLiteral("org.neard.Record"));
268 reply.waitForFinished();
269 if (reply.isError())
270 return QNdefRecord();
271
272 const QString &value = reply.value().value(QStringLiteral("Representation")).toString();
273 const QString &locale = reply.value().value(QStringLiteral("Language")).toString();
274 const QString &encoding = reply.value().value(QStringLiteral("Encoding")).toString();
275 const QString &uri = reply.value().value(QStringLiteral("URI")).toString();
276
277// const QString &mime = reply.value().value(QStringLiteral("MIME")).toString();
278// const QString &arr = reply.value().value(QStringLiteral("ARR")).toString();
279
280 const QString type = reply.value().value(QStringLiteral("Type")).toString();
281 if (type == QStringLiteral("Text")) {
282 QNdefNfcTextRecord textRecord;
283 textRecord.setText(value);
284 textRecord.setLocale(locale);
285 textRecord.setEncoding((encoding == QStringLiteral("UTF-8")) ? QNdefNfcTextRecord::Utf8
286 : QNdefNfcTextRecord::Utf16);
287 return textRecord;
288 } else if (type == QStringLiteral("SmartPoster")) {
289 QNdefNfcSmartPosterRecord spRecord;
290 if (!value.isEmpty()) {
291 spRecord.addTitle(value, locale, (encoding == QStringLiteral("UTF-8"))
292 ? QNdefNfcTextRecord::Utf8
293 : QNdefNfcTextRecord::Utf16);
294 }
295
296 if (!uri.isEmpty())
297 spRecord.setUri(QUrl(uri));
298
299 const QString &action = reply.value().value(QStringLiteral("Action")).toString();
300 if (!action.isEmpty()) {
301 if (action == QStringLiteral("Do"))
302 spRecord.setAction(QNdefNfcSmartPosterRecord::DoAction);
303 else if (action == QStringLiteral("Save"))
304 spRecord.setAction(QNdefNfcSmartPosterRecord::SaveAction);
305 else if (action == QStringLiteral("Edit"))
306 spRecord.setAction(QNdefNfcSmartPosterRecord::EditAction);
307 }
308
309 if (reply.value().contains(QStringLiteral("Size"))) {
310 uint size = reply.value().value(QStringLiteral("Size")).toUInt();
311 spRecord.setSize(size);
312 }
313
314 const QString &mimeType = reply.value().value(QStringLiteral("MIMEType")).toString();
315 if (!mimeType.isEmpty()) {
316 spRecord.setTypeInfo(mimeType.toUtf8());
317 }
318
319
320 return spRecord;
321 } else if (type == QStringLiteral("URI")) {
322 QNdefNfcUriRecord uriRecord;
323 uriRecord.setUri(QUrl(uri));
324 return uriRecord;
325 } else if (type == QStringLiteral("MIME")) {
326
327 } else if (type == QStringLiteral("AAR")) {
328
329 }
330
331 return QNdefRecord();
332 }
333
334 void handleRecordFound(const QDBusObjectPath &path)
335 {
336 m_recordPaths.append(t: path);
337 // FIXME: this timer only exists because neard doesn't currently supply enough
338 // information to let us know when all record interfaces have been added or
339 // how many records are actually contained on a tag. We assume that when no
340 // signal has been received for 100ms all record interfaces have been added.
341 m_recordPathsCollectedTimer.start(msec: 100);
342 // as soon as record paths have been added we can handle errors without the timer.
343 m_readErrorTimer.stop();
344 }
345
346 void createNdefMessage()
347 {
348 if (m_readRequested) {
349 qCDebug(QT_NFC_NEARD) << "creating Ndef message, reading" << m_recordPaths.length() << "record paths";
350 QNdefMessage newNdefMessage;
351 for (const QDBusObjectPath &recordPath : qAsConst(t&: m_recordPaths))
352 newNdefMessage.append(readRecord(path: recordPath));
353
354 if (!newNdefMessage.isEmpty()) {
355 QMetaObject::invokeMethod(this, "ndefMessageRead", Qt::QueuedConnection,
356 Q_ARG(QNdefMessage, newNdefMessage));
357 // the request id in requestCompleted has to match the one created in readNdefMessages
358 QMetaObject::invokeMethod(this, [this]() {
359 Q_EMIT this->requestCompleted(this->m_currentReadRequestId);
360 }, Qt::QueuedConnection);
361 } else {
362 this->reportError(QNearFieldTarget::UnknownError, m_currentReadRequestId);
363 }
364
365 m_readRequested = false;
366 // invalidate the current request id
367 m_currentReadRequestId = QNearFieldTarget::RequestId(0);
368 }
369 }
370
371 void handleReadError()
372 {
373 emit QNearFieldTarget::error(error: QNearFieldTarget::UnknownError, id: m_currentReadRequestId);
374 m_currentReadRequestId = QNearFieldTarget::RequestId(0);
375 }
376
377 void handleWriteRequest()
378 {
379 OrgNeardTagInterface tagInterface(QStringLiteral("org.neard"),
380 m_tagPath.path(),
381 QDBusConnection::systemBus());
382 if (!tagInterface.isValid()) {
383 qCWarning(QT_NFC_NEARD) << "tag interface invalid";
384 } else {
385 QDBusPendingReply<> reply;
386 reply = tagInterface.Write(attributes: m_currentWriteRequestData);
387 reply.waitForFinished();
388 if (reply.isError()) {
389 qCWarning(QT_NFC_NEARD) << "Error writing to NFC tag" << reply.error();
390 this->reportError(QNearFieldTarget::UnknownError, m_currentWriteRequestId);
391 }
392
393 QMetaObject::invokeMethod(this, "ndefMessagesWritten", Qt::QueuedConnection);
394 QMetaObject::invokeMethod(this, [this]() {
395 Q_EMIT this->requestCompleted(this->m_currentWriteRequestId);
396 }, Qt::QueuedConnection);
397 }
398
399 // invalidate current write request
400 m_currentWriteRequestId = QNearFieldTarget::RequestId(0);
401 }
402
403protected:
404 QDBusObjectPath m_tagPath;
405 OrgFreedesktopDBusPropertiesInterface *m_dbusProperties;
406 QList<QDBusObjectPath> m_recordPaths;
407 QTimer m_recordPathsCollectedTimer;
408 QTimer m_readErrorTimer;
409 QTimer m_delayedWriteTimer;
410 QNearFieldTarget::Type m_type;
411 bool m_readRequested;
412 QNearFieldTarget::RequestId m_currentReadRequestId;
413 QNearFieldTarget::RequestId m_currentWriteRequestId;
414 QVariantMap m_currentWriteRequestData;
415};
416
417QT_END_NAMESPACE
418
419#endif // QNEARFIELDTARGET_NEARD_P_H
420

source code of qtconnectivity/src/nfc/qnearfieldtarget_neard_p.h