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 bool success = error == nil;
274 QMetaObject::invokeMethod(this, [this, success] {
275 requestInProgress = QNearFieldTarget::RequestId();
276 if (success) {
277 connected = true;
278 onExecuteRequest();
279 } else {
280 const auto requestId = queue.dequeue().first;
282 invalidate();
283 }
284 });
285 }];
286 return true;
287 }
288
289 return false;
290}
291
292bool QNearFieldTargetPrivateImpl::isNdefTag() const
293{
294 const id tag = static_cast<id>(nfcTag.get());
295 if ([tag conformsToProtocol:@protocol(NFCMiFareTag)])
297 if ([tag conformsToProtocol:@protocol(NFCFeliCaTag)])
299 if ([tag conformsToProtocol:@protocol(NFCISO15693Tag)])
301 if ([tag conformsToProtocol:@protocol(NFCISO7816Tag)])
303 return [tag conformsToProtocol:@protocol(NFCNDEFTag)];
304}
305
306void QNearFieldTargetPrivateImpl::onTargetCheck()
307{
308 if (!isAvailable())
309 invalidate();
310}
311
312void QNearFieldTargetPrivateImpl::onTargetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
313{
314 Q_UNUSED(id);
315
317 invalidate();
318}
319
320namespace {
321
322QNdefMessage ndefToQtNdefMessage(NFCNDEFMessage *nativeMessage)
323{
324 if (!nativeMessage)
325 return {};
326
327 QList<QNdefRecord> ndefRecords;
328 for (NFCNDEFPayload *ndefRecord in nativeMessage.records) {
329 QNdefRecord qtNdefRecord;
330 if (ndefRecord.typeNameFormat != NFCTypeNameFormatUnchanged) // Does not match anything in Qt.
331 qtNdefRecord.setTypeNameFormat(QNdefRecord::TypeNameFormat(ndefRecord.typeNameFormat));
332 if (ndefRecord.identifier)
333 qtNdefRecord.setId(QByteArray::fromNSData(ndefRecord.identifier));
334 if (ndefRecord.type)
335 qtNdefRecord.setType(QByteArray::fromNSData(ndefRecord.type));
336 if (ndefRecord.payload)
337 qtNdefRecord.setPayload(QByteArray::fromNSData(ndefRecord.payload));
338 ndefRecords.push_back(qtNdefRecord);
339 }
340
341 return QNdefMessage{ndefRecords};
342}
343
344} // Unnamed namespace.
345
346void QNearFieldTargetPrivateImpl::onExecuteRequest()
347{
348 if (!nfcTag || requestInProgress.isValid())
349 return;
350
351 if (isNdefTag()) {
352 if (ndefOperations.empty())
353 return;
354
355 auto *ndefDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate);
356 Q_ASSERT(ndefDelegate);
357
358 Q_ASSERT(qt_Nfc_Queue()); // This is where callbacks get called.
359
360 const auto op = ndefOperations.front();
361 ndefOperations.pop_front();
362 requestInProgress = op.requestId;
363 auto requestId = requestInProgress; // Copy so we capture by value in the block.
364
365 id<NFCNDEFTag> ndefTag = static_cast<id<NFCNDEFTag>>(nfcTag.get());
366
367 std::unique_ptr<QNfcNdefNotifier> guard(new QNfcNdefNotifier);
368 auto *cbNotifier = guard.get();
369
372
373 if (op.type == NdefOperation::Read) {
375 this, &QNearFieldTargetPrivateImpl::messageRead,
377
378 // We call it here, but the callback will be executed on an unspecified thread.
379 [ndefTag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable msg, NSError * _Nullable err) {
380 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
381 if (err) {
382 NSLog(@"Reading NDEF messaged ended with error: %@", err);
383 emit cbNotifier->tagError(QNearFieldTarget::NdefReadError, requestId);
384 return;
385 }
386
387 const QNdefMessage ndefMessage(ndefToQtNdefMessage(msg));
388 emit cbNotifier->ndefMessageRead(ndefMessage, requestId);
389 }];
390 } else {
392 this, &QNearFieldTargetPrivateImpl::messageWritten,
394
395 NSData *ndefData = op.message.toByteArray().toNSData(); // autoreleased.
396 Q_ASSERT(ndefData);
397
398 NFCNDEFMessage *ndefMessage = [NFCNDEFMessage ndefMessageWithData:ndefData]; // autoreleased.
399 Q_ASSERT(ndefMessage);
400
401 [ndefTag writeNDEF:ndefMessage completionHandler:^(NSError *err) {
402 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
403 if (err) {
404 NSLog(@"Writing NDEF messaged ended with error: %@", err);
405 emit cbNotifier->tagError(QNearFieldTarget::NdefWriteError, requestId);
406 return;
407 }
408
409 emit cbNotifier->ndefMessageWritten(requestId);
410 }];
411 }
412 guard.release(); // Owned by the completion handler now.
413 return;
414 }
415
416 if (@available(iOS 13, *)) {
417 if (queue.isEmpty())
418 return;
419 const auto request = queue.dequeue();
420 requestInProgress = request.first;
421 const auto tag = static_cast<id<NFCISO7816Tag>>(nfcTag.get());
422 auto *apdu = [[[NFCISO7816APDU alloc] initWithData: request.second.toNSData()] autorelease];
423 [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){
424 QByteArray recvBuffer = QByteArray::fromNSData(responseData);
425 recvBuffer += static_cast<char>(sw1);
426 recvBuffer += static_cast<char>(sw2);
427 const bool success = error == nil;
428 responseProvider->provideResponse(request.first, success, recvBuffer);
429 }];
430 }
431}
432
433void QNearFieldTargetPrivateImpl::onResponseReceived(QNearFieldTarget::RequestId requestId, bool success, QByteArray recvBuffer)
434{
435 if (requestInProgress != requestId)
436 return;
437
438 requestInProgress = QNearFieldTarget::RequestId();
439 if (success) {
440 setResponseForRequest(requestId, recvBuffer, true);
441 onExecuteRequest();
442 } else {
444 invalidate();
445 }
446}
447
448void QNearFieldTargetPrivateImpl::messageRead(const QNdefMessage &message, QNearFieldTarget::RequestId requestId)
449{
450 hasNDEFMessage = message.size() != 0;
451
452 setResponseForRequest(requestId, message.toByteArray(), true);
453 requestInProgress = {}; // Invalidating, so we can execute the next one.
454 onExecuteRequest();
455
457}
458
459void QNearFieldTargetPrivateImpl::messageWritten(QNearFieldTarget::RequestId requestId)
460{
461 requestInProgress = {}; // Invalidating, so we can execute the next one.
462 onExecuteRequest();
463
465}
466
\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...