Qt 6.x
The Qt SDK
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
95{
96 if (!nfcTag || isNdefTag()) // NFCNDEFTag does not have this information ...
97 return {};
98
99 if (@available(iOS 13, *)) {
100 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
101 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
102 if (iso7816Tag)
103 return QByteArray::fromNSData(iso7816Tag.identifier);
104 }
105
106 return {};
107}
108
110{
111 if (!nfcTag || isNdefTag()) // No information provided by NFCNDEFTag.
113
114 if (@available(iOS 13, *)) {
115 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
116 id<NFCISO7816Tag> iso7816Tag = tag.asNFCISO7816Tag;
117
118 if (tag.type != NFCTagTypeISO7816Compatible || iso7816Tag == nil)
120
121 if (iso7816Tag.historicalBytes != nil && iso7816Tag.applicationData == nil)
123
124 if (iso7816Tag.historicalBytes == nil && iso7816Tag.applicationData != nil)
126
128 }
129
131}
132
133QNearFieldTarget::AccessMethods QNearFieldTargetPrivateImpl::accessMethods() const
134{
135 if (isNdefTag())
137
138 if (@available(iOS 13, *)) {
139 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
140 if (tag && [tag conformsToProtocol:@protocol(NFCISO7816Tag)])
141 return QNearFieldTarget::TagTypeSpecificAccess;
142 }
143
145}
146
148{
150 return 0xFEFF;
151
152 // TODO: check if 'capacity' of NFCNDEFTag can be used?
153 return 0;
154}
155
157{
159
162 return requestId;
163 }
164
165 queue.enqueue(std::pair(requestId, command));
166
167 if (!connect()) {
169 return requestId;
170 }
171
172 onExecuteRequest();
173 return requestId;
174}
175
177{
178 return hasNDEFMessage;
179}
180
182{
183 hasNDEFMessage = false;
184
186
187 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
188 qCWarning(QT_IOS_NFC, "Target does not allow to read NDEF messages, "
189 "was not detected as NDEF tag by the reader session?");
191 return requestId;
192 }
193
194 NdefOperation op;
196 op.requestId = requestId;
197
198 ndefOperations.push_back(op);
199 onExecuteRequest();
200
201 return requestId;
202}
203
205{
207
208 if (!(accessMethods() & QNearFieldTarget::NdefAccess) || !isNdefTag()) {
209 qCWarning(QT_IOS_NFC, "Target does not allow to write NDEF messages, "
210 "was not detected as NDEF tag by the reader session?");
212 return requestId;
213 }
214
215 if (messages.size() != 1) {
216 // The native framework does not allow to write 'messages', only _one_ message
217 // at a time. Not to multiply the complexity of having 'ndefOperations' queue
218 // with some queue inside the delegate's code (plus some unpredictable errors
219 // handling) - require a single message as a single request.
220 qCWarning(QT_IOS_NFC, "Only one NDEF message per request ID can be written");
221 return requestId;
222 }
223
224 NdefOperation op;
226 op.requestId = requestId;
227 op.message = messages.first();
228
229 ndefOperations.push_back(op);
230 onExecuteRequest();
231
232 return requestId;
233}
234
236{
237 if (requestInProgress.isValid())
238 return true;
239
240 const auto tagIsAvailable = [this](auto tag) {
241 return tag && (!connected || tag.available);
242 };
243
244 if (isNdefTag())
245 return tagIsAvailable(static_cast<id<NFCNDEFTag>>(nfcTag.get()));
246
247 if (@available(iOS 13, *))
248 return tagIsAvailable(static_cast<id<NFCTag>>(nfcTag.get()));
249
250 return false;
251}
252
254{
255 if (connected || requestInProgress.isValid())
256 return true;
257
258 if (isNdefTag())
259 return connected = true;
260
261 if (!isAvailable() || queue.isEmpty())
262 return false;
263
264 if (@available(iOS 13, *)) {
265 requestInProgress = queue.head().first;
266 id<NFCTag> tag = static_cast<id<NFCTag>>(nfcTag.get());
267 NFCTagReaderSession* session = tag.session;
268 [session connectToTag: tag completionHandler: ^(NSError* error){
269 const bool success = error == nil;
270 QMetaObject::invokeMethod(this, [this, success] {
271 requestInProgress = QNearFieldTarget::RequestId();
272 if (success) {
273 connected = true;
274 onExecuteRequest();
275 } else {
276 const auto requestId = queue.dequeue().first;
277 invalidate();
278 Q_EMIT targetLost(this);
280 }
281 });
282 }];
283 return true;
284 }
285
286 return false;
287}
288
289bool QNearFieldTargetPrivateImpl::isNdefTag() const
290{
291 return [static_cast<id>(nfcTag.get()) conformsToProtocol:@protocol(NFCNDEFTag)];
292}
293
294void QNearFieldTargetPrivateImpl::onTargetCheck()
295{
296 if (!isAvailable()) {
297 invalidate();
298 Q_EMIT targetLost(this);
299 }
300}
301
302void QNearFieldTargetPrivateImpl::onTargetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
303{
304 Q_UNUSED(id);
305
307 invalidate();
308 Q_EMIT targetLost(this);
309 }
310}
311
312namespace {
313
314QNdefMessage ndefToQtNdefMessage(NFCNDEFMessage *nativeMessage)
315{
316 if (!nativeMessage)
317 return {};
318
319 QList<QNdefRecord> ndefRecords;
320 for (NFCNDEFPayload *ndefRecord in nativeMessage.records) {
321 QNdefRecord qtNdefRecord;
322 if (ndefRecord.typeNameFormat != NFCTypeNameFormatUnchanged) // Does not match anything in Qt.
323 qtNdefRecord.setTypeNameFormat(QNdefRecord::TypeNameFormat(ndefRecord.typeNameFormat));
324 if (ndefRecord.identifier)
325 qtNdefRecord.setId(QByteArray::fromNSData(ndefRecord.identifier));
326 if (ndefRecord.type)
327 qtNdefRecord.setType(QByteArray::fromNSData(ndefRecord.type));
328 if (ndefRecord.payload)
329 qtNdefRecord.setPayload(QByteArray::fromNSData(ndefRecord.payload));
330 ndefRecords.push_back(qtNdefRecord);
331 }
332
333 return QNdefMessage{ndefRecords};
334}
335
336} // Unnamed namespace.
337
338void QNearFieldTargetPrivateImpl::onExecuteRequest()
339{
340 if (!nfcTag || requestInProgress.isValid())
341 return;
342
343 if (isNdefTag()) {
344 if (ndefOperations.empty())
345 return;
346
347 auto *ndefDelegate = static_cast<QIosNfcNdefSessionDelegate *>(sessionDelegate);
348 Q_ASSERT(ndefDelegate);
349
350 Q_ASSERT(qt_Nfc_Queue()); // This is where callbacks get called.
351
352 const auto op = ndefOperations.front();
353 ndefOperations.pop_front();
354 requestInProgress = op.requestId;
355 auto requestId = requestInProgress; // Copy so we capture by value in the block.
356
357 id<NFCNDEFTag> ndefTag = static_cast<id<NFCNDEFTag>>(nfcTag.get());
358
359 std::unique_ptr<QNfcNdefNotifier> guard(new QNfcNdefNotifier);
360 auto *cbNotifier = guard.get();
361
364
365 if (op.type == NdefOperation::Read) {
367 this, &QNearFieldTargetPrivateImpl::messageRead,
369
370 // We call it here, but the callback will be executed on an unspecified thread.
371 [ndefTag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable msg, NSError * _Nullable err) {
372 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
373 if (err) {
374 NSLog(@"Reading NDEF messaged ended with error: %@", err);
375 emit cbNotifier->tagError(QNearFieldTarget::NdefReadError, requestId);
376 return;
377 }
378
379 const QNdefMessage ndefMessage(ndefToQtNdefMessage(msg));
380 emit cbNotifier->ndefMessageRead(ndefMessage, requestId);
381 }];
382 } else {
384 this, &QNearFieldTargetPrivateImpl::messageWritten,
386
387 NSData *ndefData = op.message.toByteArray().toNSData(); // autoreleased.
388 Q_ASSERT(ndefData);
389
390 NFCNDEFMessage *ndefMessage = [NFCNDEFMessage ndefMessageWithData:ndefData]; // autoreleased.
391 Q_ASSERT(ndefMessage);
392
393 [ndefTag writeNDEF:ndefMessage completionHandler:^(NSError *err) {
394 const std::unique_ptr<QNfcNdefNotifier> notifierGuard(cbNotifier);
395 if (err) {
396 NSLog(@"Writing NDEF messaged ended with error: %@", err);
397 emit cbNotifier->tagError(QNearFieldTarget::NdefWriteError, requestId);
398 return;
399 }
400
401 emit cbNotifier->ndefMessageWritten(requestId);
402 }];
403 }
404 guard.release(); // Owned by the completion handler now.
405 return;
406 }
407
408 if (@available(iOS 13, *)) {
409 if (queue.isEmpty())
410 return;
411 const auto request = queue.dequeue();
412 requestInProgress = request.first;
413 const auto tag = static_cast<id<NFCISO7816Tag>>(nfcTag.get());
414 auto *apdu = [[[NFCISO7816APDU alloc] initWithData: request.second.toNSData()] autorelease];
415 [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){
416 QByteArray recvBuffer = QByteArray::fromNSData(responseData);
417 recvBuffer += static_cast<char>(sw1);
418 recvBuffer += static_cast<char>(sw2);
419 const bool success = error == nil;
420 responseProvider->provideResponse(request.first, success, recvBuffer);
421 }];
422 }
423}
424
425void QNearFieldTargetPrivateImpl::onResponseReceived(QNearFieldTarget::RequestId requestId, bool success, QByteArray recvBuffer)
426{
427 if (requestInProgress != requestId)
428 return;
429
430 requestInProgress = QNearFieldTarget::RequestId();
431 if (success) {
432 setResponseForRequest(requestId, recvBuffer, true);
433 onExecuteRequest();
434 } else {
435 invalidate();
436 Q_EMIT targetLost(this);
438 }
439}
440
441void QNearFieldTargetPrivateImpl::messageRead(const QNdefMessage &message, QNearFieldTarget::RequestId requestId)
442{
443 hasNDEFMessage = message.size() != 0;
444
445 setResponseForRequest(requestId, message.toByteArray(), true);
446 requestInProgress = {}; // Invalidating, so we can execute the next one.
447 onExecuteRequest();
448
450}
451
452void QNearFieldTargetPrivateImpl::messageWritten(QNearFieldTarget::RequestId requestId)
453{
454 requestInProgress = {}; // Invalidating, so we can execute the next one.
455 onExecuteRequest();
456
458}
459
\inmodule QtCore
Definition qbytearray.h:57
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & first()
Definition qlist.h:628
void push_back(parameter_type t)
Definition qlist.h:672
void clear()
Definition qlist.h:417
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
void setPayload(const QByteArray &payload)
Sets the payload of the NDEF record to payload.
void setId(const QByteArray &id)
Sets the id of the NDEF record to id.
TypeNameFormat
This enum describes the type name format of an NDEF record.
Definition qndefrecord.h:18
void setType(const QByteArray &type)
Sets the type of the NDEF record to type.
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:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3099
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:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
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,...)
GLenum GLuint id
[7]
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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent