Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qnearfieldtarget_ios.mm
Go to the documentation of this file.
1// Copyright (C) 2020 Governikus GmbH & Co. KG
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
8
9#import <CoreNFC/NFCNDEFReaderSession.h>
10#import <CoreNFC/NFCReaderSession.h>
11#import <CoreNFC/NFCTagReaderSession.h>
12#import <CoreNFC/NFCISO7816Tag.h>
13#import <CoreNFC/NFCTag.h>
14
15#include <QtCore/qapplicationstatic.h>
16#include <QtCore/qloggingcategory.h>
17
19
21
22void ResponseProvider::provideResponse(QNearFieldTarget::RequestId requestId, bool success, QByteArray recvBuffer) {
23 Q_EMIT responseReceived(requestId, success, recvBuffer);
24}
25
27{
28 id some = static_cast<id>(obj);
29
30 if ([some conformsToProtocol:@protocol(NFCNDEFTag)])
31 [static_cast<id<NFCNDEFTag>>(some) release];
32 else if ([some conformsToProtocol:@protocol(NFCTag)])
33 [static_cast<id<NFCTag>>(some) release];
34 else
35 Q_UNREACHABLE();
36}
37
40 nfcTag(tag)
41{
42 Q_ASSERT(nfcTag);
43
44 QObject::connect(this, &QNearFieldTargetPrivate::error, this, &QNearFieldTargetPrivateImpl::onTargetError);
45 QObject::connect(responseProvider, &ResponseProvider::responseReceived, this, &QNearFieldTargetPrivateImpl::onResponseReceived);
46 QObject::connect(&targetCheckTimer, &QTimer::timeout, this, &QNearFieldTargetPrivateImpl::onTargetCheck);
48}
49
52 nfcTag(tag)
53{
54 Q_ASSERT(delegate && tag);
55 Q_ASSERT([id(tag) conformsToProtocol:@protocol(NFCNDEFTag)]);
56
57 auto qtDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate = delegate);
58 notifier = [qtDelegate ndefNotifier];
59 Q_ASSERT(notifier);
60
61 // The 'notifier' lives on a (potentially different, unspecified) thread,
62 // thus connection is 'queued'.
65
66 QObject::connect(this, &QNearFieldTargetPrivate::error, this, &QNearFieldTargetPrivateImpl::onTargetError);
67 QObject::connect(&targetCheckTimer, &QTimer::timeout, this, &QNearFieldTargetPrivateImpl::onTargetCheck);
68
70}
71
73{
74}
75
77{
78 queue.clear();
79 ndefOperations.clear();
80
81 if (isNdefTag()) {
82 Q_ASSERT(notifier);
83
84 QObject::disconnect(notifier, nullptr, this, nullptr);
85 notifier = nullptr;
86 }
87
88 nfcTag.reset();
89 sessionDelegate = nil;
90
92
93 QMetaObject::invokeMethod(this, [this]() {
94 Q_EMIT targetLost(this);
96}
97
99{
100 if (!nfcTag || isNdefTag()) // NFCNDEFTag does not have this information ...
101 return {};
102
103 if (@available(iOS 13, *)) {
104 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
105 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
106 if (iso7816Tag)
107 return QByteArray::fromNSData(iso7816Tag.identifier);
108 }
109
110 return {};
111}
112
114{
115 if (!nfcTag || isNdefTag()) // No information provided by NFCNDEFTag.
117
118 if (@available(iOS 13, *)) {
119 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
120 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
121
122 if (tag.type != NFCTagTypeISO7816Compatible || iso7816Tag == nil)
124
125 if (iso7816Tag.historicalBytes != nil && iso7816Tag.applicationData == nil)
127
128 if (iso7816Tag.historicalBytes == nil && iso7816Tag.applicationData != nil)
130
132 }
133
135}
136
137QNearFieldTarget::AccessMethods QNearFieldTargetPrivateImpl::accessMethods() const
138{
139 if (isNdefTag())
141
142 if (@available(iOS 13, *)) {
143 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
144 if (tag && [tag conformsToProtocol:@protocol(NFCISO7816Tag)])
145 return QNearFieldTarget::TagTypeSpecificAccess;
146 }
147
149}
150
152{
154 return 0xFEFF;
155
156 // TODO: check if 'capacity' of NFCNDEFTag can be used?
157 return 0;
158}
159
161{
163
166 return requestId;
167 }
168
169 queue.enqueue(std::pair(requestId, command));
170
171 if (!connect()) {
173 return requestId;
174 }
175
176 onExecuteRequest();
177 return requestId;
178}
179
181{
182 return hasNDEFMessage;
183}
184
186{
187 hasNDEFMessage = false;
188
190
191 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
192 qCWarning(QT_IOS_NFC, "Target does not allow to read NDEF messages, "
193 "was not detected as NDEF tag by the reader session?");
195 return requestId;
196 }
197
198 NdefOperation op;
200 op.requestId = requestId;
201
202 ndefOperations.push_back(op);
203 onExecuteRequest();
204
205 return requestId;
206}
207
209{
211
212 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
213 qCWarning(QT_IOS_NFC, "Target does not allow to write NDEF messages, "
214 "was not detected as NDEF tag by the reader session?");
216 return requestId;
217 }
218
219 if (messages.size() != 1) {
220 // The native framework does not allow to write 'messages', only _one_ message
221 // at a time. Not to multiply the complexity of having 'ndefOperations' queue
222 // with some queue inside the delegate's code (plus some unpredictable errors
223 // handling) - require a single message as a single request.
224 qCWarning(QT_IOS_NFC, "Only one NDEF message per request ID can be written");
225 return requestId;
226 }
227
228 NdefOperation op;
230 op.requestId = requestId;
231 op.message = messages.first();
232
233 ndefOperations.push_back(op);
234 onExecuteRequest();
235
236 return requestId;
237}
238
240{
241 if (requestInProgress.isValid())
242 return true;
243
244 const auto tagIsAvailable = [this](auto tag) {
245 return tag && (!connected || tag.available);
246 };
247
248 if (isNdefTag())
249 return tagIsAvailable(static_cast<id<NFCNDEFTag>>(nfcTag.get()));
250
251 if (@available(iOS 13, *))
252 return tagIsAvailable(static_cast<id<NFCTag>>(nfcTag.get()));
253
254 return false;
255}
256
258{
259 if (connected || requestInProgress.isValid())
260 return true;
261
262 if (isNdefTag())
263 return connected = true;
264
265 if (!isAvailable() || queue.isEmpty())
266 return false;
267
268 if (@available(iOS 13, *)) {
269 requestInProgress = queue.head().first;
270 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
271 NFCTagReaderSession* session = tag.session;
272 [session connectToTag: tag completionHandler: ^(NSError* error){
273 const int errorCode = error == nil ? -1 : error.code;
274 QMetaObject::invokeMethod(this, [this, errorCode] {
275 requestInProgress = QNearFieldTarget::RequestId();
276 if (errorCode == -1) {
277 connected = true;
278 onExecuteRequest();
279 } else {
280 const auto requestId = queue.dequeue().first;
282 errorCode == NFCReaderError::NFCReaderErrorSecurityViolation
284 : QNearFieldTarget::ConnectionError,
285 requestId);
286 invalidate();
287 }
288 });
289 }];
290 return true;
291 }
292
293 return false;
294}
295
296bool QNearFieldTargetPrivateImpl::isNdefTag() const
297{
298 const id tag = static_cast<id>(nfcTag.get());
299 if ([tag conformsToProtocol:@protocol(NFCMiFareTag)])
301 if ([tag conformsToProtocol:@protocol(NFCFeliCaTag)])
303 if ([tag conformsToProtocol:@protocol(NFCISO15693Tag)])
305 if ([tag conformsToProtocol:@protocol(NFCISO7816Tag)])
307 return [tag conformsToProtocol:@protocol(NFCNDEFTag)];
308}
309
310void QNearFieldTargetPrivateImpl::onTargetCheck()
311{
312 if (!isAvailable())
313 invalidate();
314}
315
316void QNearFieldTargetPrivateImpl::onTargetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
317{
318 Q_UNUSED(id);
319
321 invalidate();
322}
323
324namespace {
325
326QNdefMessage ndefToQtNdefMessage(NFCNDEFMessage *nativeMessage)
327{
328 if (!nativeMessage)
329 return {};
330
331 QList<QNdefRecord> ndefRecords;
332 for (NFCNDEFPayload *ndefRecord in nativeMessage.records) {
333 QNdefRecord qtNdefRecord;
334 if (ndefRecord.typeNameFormat != NFCTypeNameFormatUnchanged) // Does not match anything in Qt.
335 qtNdefRecord.setTypeNameFormat(QNdefRecord::TypeNameFormat(ndefRecord.typeNameFormat));
336 if (ndefRecord.identifier)
337 qtNdefRecord.setId(QByteArray::fromNSData(ndefRecord.identifier));
338 if (ndefRecord.type)
339 qtNdefRecord.setType(QByteArray::fromNSData(ndefRecord.type));
340 if (ndefRecord.payload)
341 qtNdefRecord.setPayload(QByteArray::fromNSData(ndefRecord.payload));
342 ndefRecords.push_back(qtNdefRecord);
343 }
344
345 return QNdefMessage{ndefRecords};
346}
347
348} // Unnamed namespace.
349
350void QNearFieldTargetPrivateImpl::onExecuteRequest()
351{
352 if (!nfcTag || requestInProgress.isValid())
353 return;
354
355 if (isNdefTag()) {
356 if (ndefOperations.empty())
357 return;
358
359 auto *ndefDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate);
360 Q_ASSERT(ndefDelegate);
361
362 Q_ASSERT(qt_Nfc_Queue()); // This is where callbacks get called.
363
364 const auto op = ndefOperations.front();
365 ndefOperations.pop_front();
366 requestInProgress = op.requestId;
367 auto requestId = requestInProgress; // Copy so we capture by value in the block.
368
369 id<NFCNDEFTag> ndefTag = static_cast<id<NFCNDEFTag>>(nfcTag.get());
370
371 std::unique_ptr<QNfcNdefNotifier> guard(new QNfcNdefNotifier);
372 auto *cbNotifier = guard.get();
373
376
377 if (op.type == NdefOperation::Read) {
379 this, &QNearFieldTargetPrivateImpl::messageRead,
381
382 // We call it here, but the callback will be executed on an unspecified thread.
383 [ndefTag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable msg, NSError * _Nullable err) {
384 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
385 if (err) {
386 NSLog(@"Reading NDEF messaged ended with error: %@", err);
387 emit cbNotifier->tagError(QNearFieldTarget::NdefReadError, requestId);
388 return;
389 }
390
391 const QNdefMessage ndefMessage(ndefToQtNdefMessage(msg));
392 emit cbNotifier->ndefMessageRead(ndefMessage, requestId);
393 }];
394 } else {
396 this, &QNearFieldTargetPrivateImpl::messageWritten,
398
399 NSData *ndefData = op.message.toByteArray().toNSData(); // autoreleased.
400 Q_ASSERT(ndefData);
401
402 NFCNDEFMessage *ndefMessage = [NFCNDEFMessage ndefMessageWithData:ndefData]; // autoreleased.
403 Q_ASSERT(ndefMessage);
404
405 [ndefTag writeNDEF:ndefMessage completionHandler:^(NSError *err) {
406 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
407 if (err) {
408 NSLog(@"Writing NDEF messaged ended with error: %@", err);
409 emit cbNotifier->tagError(QNearFieldTarget::NdefWriteError, requestId);
410 return;
411 }
412
413 emit cbNotifier->ndefMessageWritten(requestId);
414 }];
415 }
416 guard.release(); // Owned by the completion handler now.
417 return;
418 }
419
420 if (@available(iOS 13, *)) {
421 if (queue.isEmpty())
422 return;
423 const auto request = queue.dequeue();
424 requestInProgress = request.first;
425 const auto tag = static_cast<id<NFCISO7816Tag>>(nfcTag.get());
426 auto *apdu = [[[NFCISO7816APDU alloc] initWithData: request.second.toNSData()] autorelease];
427 [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){
428 QByteArray recvBuffer = QByteArray::fromNSData(responseData);
429 recvBuffer += static_cast<char>(sw1);
430 recvBuffer += static_cast<char>(sw2);
431 const bool success = error == nil;
432 responseProvider->provideResponse(request.first, success, recvBuffer);
433 }];
434 }
435}
436
437void QNearFieldTargetPrivateImpl::onResponseReceived(QNearFieldTarget::RequestId requestId, bool success, QByteArray recvBuffer)
438{
439 if (requestInProgress != requestId)
440 return;
441
442 requestInProgress = QNearFieldTarget::RequestId();
443 if (success) {
444 setResponseForRequest(requestId, recvBuffer, true);
445 onExecuteRequest();
446 } else {
448 invalidate();
449 }
450}
451
452void QNearFieldTargetPrivateImpl::messageRead(const QNdefMessage &message, QNearFieldTarget::RequestId requestId)
453{
454 hasNDEFMessage = message.size() != 0;
455
456 setResponseForRequest(requestId, message.toByteArray(), true);
457 requestInProgress = {}; // Invalidating, so we can execute the next one.
458 onExecuteRequest();
459
461}
462
463void QNearFieldTargetPrivateImpl::messageWritten(QNearFieldTarget::RequestId requestId)
464{
465 requestInProgress = {}; // Invalidating, so we can execute the next one.
466 onExecuteRequest();
467
469}
470
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Definition qlist.h:401
void clear()
Definition qlist.h:434
The QNdefMessage class provides an NFC NDEF message.
Q_NFC_EXPORT QByteArray toByteArray() const
Returns the NDEF message as a byte array.
The QNdefRecord class provides an NFC NDEF record.
Definition qndefrecord.h:16
TypeNameFormat
This enum describes the type name format of an NDEF record.
Definition qndefrecord.h:18
void setTypeNameFormat(TypeNameFormat typeNameFormat)
Sets the type name format of the NDEF record to typeNameFormat.
void targetLost(QNearFieldTargetPrivateImpl *target)
QNearFieldTarget::RequestId writeNdefMessages(const QList< QNdefMessage > &messages) override
QNearFieldTarget::RequestId readNdefMessages() override
QNearFieldTargetPrivateImpl(QJniObject intent, const QByteArray uid, QObject *parent=nullptr)
void ndefMessageRead(const QNdefMessage &message, const QNearFieldTarget::RequestId &id)
QNearFieldTarget::Type type() const override
QNearFieldTarget::RequestId sendCommand(const QByteArray &command) override
QNearFieldTarget::AccessMethods accessMethods() const override
QByteArray uid() const override
void error(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
virtual void setResponseForRequest(const QNearFieldTarget::RequestId &id, const QVariant &response, bool emitRequestCompleted=true)
void requestCompleted(const QNearFieldTarget::RequestId &id)
void reportError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
\inmodule QtNfc \inheaderfile QNearFieldTarget
bool isValid() const
Returns true if this is a valid request id; otherwise returns false.
The QNearFieldTarget class provides an interface for communicating with a target device.
Type
This enum describes the type of tag the target is detected as.
Error
This enum describes the error codes that a near field target reports.
void tagError(QNearFieldTarget::Error code, QNearFieldTarget::RequestId request)
void ndefMessageRead(const QNdefMessage &message, QNearFieldTarget::RequestId request)
void ndefMessageWritten(QNearFieldTarget::RequestId request)
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void enqueue(const T &t)
Adds value t to the tail of the queue.
Definition qqueue.h:18
T & head()
Returns a reference to the queue's head item.
Definition qqueue.h:20
T dequeue()
Removes the head item in the queue and returns it.
Definition qqueue.h:19
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
void responseReceived(QNearFieldTarget::RequestId requestId, bool success, QByteArray recvBuffer)
Combined button and popup list for selecting options.
@ QueuedConnection
#define Q_APPLICATION_STATIC(TYPE, NAME,...)
AudioChannelLayoutTag tag
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError * error
QNearFieldTarget::RequestId requestId
QT_BEGIN_NAMESPACE dispatch_queue_t qt_Nfc_Queue()
#define qCWarning(category,...)
GLuint GLsizei const GLchar * message
GLhandleARB obj
[2]
GLuint in
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_EMIT
#define emit
#define Q_UNUSED(x)
sem release()
QNetworkRequest request(url)
QNearFieldTarget::RequestId requestId
enum NdefOperation::Type type
void operator()(void *tag)
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...