From fab1aef025590c4d32ee03caf92400ae8d0a3e1e Mon Sep 17 00:00:00 2001 From: Lars Schmertmann Date: Mon, 3 Aug 2020 12:01:09 +0200 Subject: Implement Qt NFC on iOS starting with TagTypeSpecificAccess NdefAccess will follow later. Fixes: QTBUG-81824 Change-Id: I87bc1b2af4b6c4aedb847c38700d766a7f755479 Reviewed-by: Timur Pocheptsov --- src/nfc/CMakeLists.txt | 16 +++ src/nfc/doc/src/nfc-index.qdoc | 2 +- src/nfc/ios/qiostagreaderdelegate.mm | 152 ++++++++++++++++++++++ src/nfc/ios/qiostagreaderdelegate_p.h | 84 ++++++++++++ src/nfc/qnearfieldmanager.cpp | 2 + src/nfc/qnearfieldmanager_ios.mm | 189 +++++++++++++++++++++++++++ src/nfc/qnearfieldmanager_ios_p.h | 109 ++++++++++++++++ src/nfc/qnearfieldtarget_ios.mm | 234 ++++++++++++++++++++++++++++++++++ src/nfc/qnearfieldtarget_ios_p.h | 104 +++++++++++++++ 9 files changed, 891 insertions(+), 1 deletion(-) create mode 100644 src/nfc/ios/qiostagreaderdelegate.mm create mode 100644 src/nfc/ios/qiostagreaderdelegate_p.h create mode 100644 src/nfc/qnearfieldmanager_ios.mm create mode 100644 src/nfc/qnearfieldmanager_ios_p.h create mode 100644 src/nfc/qnearfieldtarget_ios.mm create mode 100644 src/nfc/qnearfieldtarget_ios_p.h diff --git a/src/nfc/CMakeLists.txt b/src/nfc/CMakeLists.txt index c9d9aeed..6f851046 100644 --- a/src/nfc/CMakeLists.txt +++ b/src/nfc/CMakeLists.txt @@ -54,6 +54,22 @@ qt_internal_extend_target(Nfc CONDITION ANDROID AND NOT ANDROID_EMBEDDED Qt::Gui ) +if(IOS) + # special case begin + set(NFC_BACKEND_AVAILABLE ON) + qt_disable_apple_app_extension_api_only(Nfc) + # special case end +endif() + +qt_extend_target(Nfc CONDITION IOS + SOURCES + ios/qiostagreaderdelegate.mm ios/qiostagreaderdelegate_p.h + qnearfieldmanager_ios.mm qnearfieldmanager_ios_p.h + qnearfieldtarget_ios.mm qnearfieldtarget_ios_p.h + DEFINES + IOS_NFC +) + #### Keys ignored in scope 2:.:.:nfc.pro:ANDROID AND NOT ANDROID_EMBEDDED: # NFC_BACKEND_AVAILABLE = "yes" diff --git a/src/nfc/doc/src/nfc-index.qdoc b/src/nfc/doc/src/nfc-index.qdoc index 25a07c4b..84ac642d 100644 --- a/src/nfc/doc/src/nfc-index.qdoc +++ b/src/nfc/doc/src/nfc-index.qdoc @@ -34,7 +34,7 @@ The NFC API provides connectivity between NFC enabled devices. -Currently the API is supported on \l{Qt for Android}{Android}. +Currently the API is supported on \l{Qt for Android}{Android} and \l{Qt for iOS}{iOS}. \section1 Overview diff --git a/src/nfc/ios/qiostagreaderdelegate.mm b/src/nfc/ios/qiostagreaderdelegate.mm new file mode 100644 index 00000000..35956567 --- /dev/null +++ b/src/nfc/ios/qiostagreaderdelegate.mm @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qiostagreaderdelegate_p.h" + +#include "qnearfieldmanager_ios_p.h" + +#import +#import + +QT_USE_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QIosTagReaderDelegate) + +- (instancetype)initWithListener:(QNearFieldManagerPrivateImpl *)listener +{ + self = [super init]; + if (self) { + self.listener = listener; + self.sessionStoppedByApplication = false; + self.message = nil; + self.session = nil; + } + + return self; +} + +- (void)startSession +{ + if (self.sessionStoppedByApplication) { + Q_EMIT self.listener->didInvalidateWithError(true); + return; + } + + if (self.session) { + [self.session restartPolling]; + return; + } + + self.session = [[NFCTagReaderSession alloc] autorelease]; + self.session = [self.session initWithPollingOption:NFCPollingISO14443 delegate:self queue:nil]; + if (self.session) { + if (self.message) + self.session.alertMessage = self.message; + [self.session beginSession]; + } else { + Q_EMIT self.listener->didInvalidateWithError(true); + } +} + +- (void)stopSession:(QString)message +{ + if (self.session) { + if (self.session.ready) { + if (message.isNull()) + [self.session invalidateSession]; + else + [self.session invalidateSessionWithErrorMessage:message.toNSString()]; + self.sessionStoppedByApplication = true; + } else { + self.session = nil; + } + } +} + +- (void)alertMessage:(QString)message +{ + if (self.session && !self.sessionStoppedByApplication) + self.session.alertMessage = message.toNSString(); + else + self.message = message.toNSString(); +} + +- (void)tagReaderSessionDidBecomeActive:(NFCTagReaderSession*)session +{ + if (session != self.session) + [session invalidateSession]; +} + +- (void)tagReaderSession:(NFCTagReaderSession*)session didInvalidateWithError:(NSError*)error +{ + if (session != self.session) + return; + + self.session = nil; + if (self.sessionStoppedByApplication) { + self.sessionStoppedByApplication = false; + return; + } + + const bool doRestart = + !(error.code == NFCReaderError::NFCReaderSessionInvalidationErrorUserCanceled + || error.code == NFCReaderError::NFCReaderErrorUnsupportedFeature); + Q_EMIT self.listener->didInvalidateWithError(doRestart); +} + +- (void)tagReaderSession:(NFCTagReaderSession*)session didDetectTags:(NSArray<__kindof id>*)tags +{ + if (session != self.session) + return; + + bool foundTag = false; + for (id tag in tags) { + if (tag.type == NFCTagTypeISO7816Compatible) { + foundTag = true; + [tag retain]; + Q_EMIT self.listener->tagDiscovered(tag); + } + } + + if (!foundTag) { + [session restartPolling]; + } +} + +@end diff --git a/src/nfc/ios/qiostagreaderdelegate_p.h b/src/nfc/ios/qiostagreaderdelegate_p.h new file mode 100644 index 00000000..cb456643 --- /dev/null +++ b/src/nfc/ios/qiostagreaderdelegate_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIOSTAGREADERDELEGATE_P_H +#define QIOSTAGREADERDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#import +#import +#import + +QT_BEGIN_NAMESPACE + +class QNearFieldManagerPrivateImpl; + +QT_END_NAMESPACE + +API_AVAILABLE(ios(13.0)) +@interface QT_MANGLE_NAMESPACE(QIosTagReaderDelegate) + : NSObject + +@property QNearFieldManagerPrivateImpl *listener; +@property bool sessionStoppedByApplication; +@property (nonatomic, strong) NSString *message; +@property (nonatomic, strong) NFCTagReaderSession *session; + +- (instancetype)initWithListener:(QNearFieldManagerPrivateImpl *)listener; + +- (void)startSession; +- (void)stopSession:(QString)message; + +- (void)alertMessage:(QString)message; + +@end + +#endif // QIOSTAGREADERDELEGATE_P_H diff --git a/src/nfc/qnearfieldmanager.cpp b/src/nfc/qnearfieldmanager.cpp index 15394c1d..4796b492 100644 --- a/src/nfc/qnearfieldmanager.cpp +++ b/src/nfc/qnearfieldmanager.cpp @@ -44,6 +44,8 @@ #include "qnearfieldmanager_simulator_p.h" #elif defined(ANDROID_NFC) #include "qnearfieldmanager_android_p.h" +#elif defined(IOS_NFC) +#include "qnearfieldmanager_ios_p.h" #else #include "qnearfieldmanager_generic_p.h" #endif diff --git a/src/nfc/qnearfieldmanager_ios.mm b/src/nfc/qnearfieldmanager_ios.mm new file mode 100644 index 00000000..dcae40dc --- /dev/null +++ b/src/nfc/qnearfieldmanager_ios.mm @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnearfieldmanager_ios_p.h" + +#include "ios/qiostagreaderdelegate_p.h" +#include "qnearfieldtarget_ios_p.h" + +#include + +#import +#import +#import + +QT_BEGIN_NAMESPACE + +QNearFieldManagerPrivateImpl::QNearFieldManagerPrivateImpl() +{ + if (@available(iOS 13, *)) + delegate = [[QT_MANGLE_NAMESPACE(QIosTagReaderDelegate) alloc] initWithListener:this]; + + connect(this, &QNearFieldManagerPrivateImpl::tagDiscovered, + this, &QNearFieldManagerPrivateImpl::onTagDiscovered, + Qt::QueuedConnection); + connect(this, &QNearFieldManagerPrivateImpl::didInvalidateWithError, + this, &QNearFieldManagerPrivateImpl::onDidInvalidateWithError, + Qt::QueuedConnection); +} + +QNearFieldManagerPrivateImpl::~QNearFieldManagerPrivateImpl() +{ + if (@available(iOS 13, *)) + [delegate release]; +} + +bool QNearFieldManagerPrivateImpl::isSupported(QNearFieldTarget::AccessMethod accessMethod) const +{ + switch (accessMethod) { + case QNearFieldTarget::AnyAccess: + return NFCNDEFReaderSession.readingAvailable; + case QNearFieldTarget::TagTypeSpecificAccess: + if (@available(iOS 13, *)) + return NFCTagReaderSession.readingAvailable; + Q_FALLTHROUGH(); + case QNearFieldTarget::UnknownAccess: + case QNearFieldTarget::NdefAccess: + return false; + } +} + +bool QNearFieldManagerPrivateImpl::startTargetDetection(QNearFieldTarget::AccessMethod accessMethod) +{ + if (detectionRunning) + return false; + + switch (accessMethod) { + case QNearFieldTarget::UnknownAccess: + case QNearFieldTarget::NdefAccess: + case QNearFieldTarget::AnyAccess: + return false; + case QNearFieldTarget::TagTypeSpecificAccess: + if (@available(iOS 13, *)) + if (NFCTagReaderSession.readingAvailable) { + detectionRunning = true; + startSession(); + return true; + } + return false; + } +} + +void QNearFieldManagerPrivateImpl::stopTargetDetection(const QString &errorMessage) +{ + if (detectionRunning) { + stopSession(errorMessage); + detectionRunning = false; + Q_EMIT targetDetectionStopped(); + } +} + + +void QNearFieldManagerPrivateImpl::startSession() +{ + if (detectionRunning) + if (@available(iOS 13, *)) + [delegate startSession]; +} + +void QNearFieldManagerPrivateImpl::stopSession(const QString &error) +{ + clearTargets(); + + if (@available(iOS 13, *)) + [delegate stopSession:error]; +} + +void QNearFieldManagerPrivateImpl::clearTargets() +{ + auto i = detectedTargets.begin(); + while (i != detectedTargets.end()) { + (*i)->invalidate(); + Q_EMIT targetLost((*i)->q_ptr); + i = detectedTargets.erase(i); + } +} + + +void QNearFieldManagerPrivateImpl::setUserInformation(const QString &message) +{ + if (@available(iOS 13, *)) + [delegate alertMessage:message]; +} + +void QNearFieldManagerPrivateImpl::onTagDiscovered(void *tag) +{ + QNearFieldTargetPrivateImpl *target = new QNearFieldTargetPrivateImpl(tag); + detectedTargets += target; + connect(target, &QNearFieldTargetPrivateImpl::targetLost, + this, &QNearFieldManagerPrivateImpl::onTargetLost); + Q_EMIT targetDetected(new NearFieldTarget(target, this)); +} + +void QNearFieldManagerPrivateImpl::onTargetLost(QNearFieldTargetPrivateImpl *target) +{ + detectedTargets.removeOne(target); + Q_EMIT targetLost(target->q_ptr); + + if (detectionRunning && detectedTargets.isEmpty()) + onDidInvalidateWithError(true); +} + +void QNearFieldManagerPrivateImpl::onDidInvalidateWithError(bool doRestart) +{ + clearTargets(); + + if (detectionRunning && doRestart) + { + if (!isRestarting) { + isRestarting = true; + using namespace std::chrono_literals; + QTimer::singleShot(2s, this, [this](){ + isRestarting = false; + startSession(); + }); + } + return; + } + + detectionRunning = false; + Q_EMIT targetDetectionStopped(); +} + +QT_END_NAMESPACE diff --git a/src/nfc/qnearfieldmanager_ios_p.h b/src/nfc/qnearfieldmanager_ios_p.h new file mode 100644 index 00000000..e5d995d5 --- /dev/null +++ b/src/nfc/qnearfieldmanager_ios_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNEARFIELDMANAGER_IOS_P_H +#define QNEARFIELDMANAGER_IOS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qnearfieldmanager_p.h" + +#include + +#import + +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QIosTagReaderDelegate)); + +QT_BEGIN_NAMESPACE + +class QNearFieldTargetPrivateImpl; + +class QNearFieldManagerPrivateImpl : public QNearFieldManagerPrivate +{ + Q_OBJECT + +public: + QNearFieldManagerPrivateImpl(); + ~QNearFieldManagerPrivateImpl(); + + bool isEnabled() const override + { + return true; + } + + bool isSupported(QNearFieldTarget::AccessMethod accessMethod) const override; + + bool startTargetDetection(QNearFieldTarget::AccessMethod accessMethod) override; + void stopTargetDetection(const QString &errorMessage) override; + + void setUserInformation(const QString &message) override; + +Q_SIGNALS: + void tagDiscovered(void *tag); + void didInvalidateWithError(bool doRestart); + +private: + QT_MANGLE_NAMESPACE(QIosTagReaderDelegate) *delegate API_AVAILABLE(ios(13.0)) = nullptr; + bool detectionRunning = false; + bool isRestarting = false; + QList detectedTargets; + + void startSession(); + void stopSession(const QString &error); + void clearTargets(); + +private Q_SLOTS: + void onTagDiscovered(void *target); + void onTargetLost(QNearFieldTargetPrivateImpl *target); + void onDidInvalidateWithError(bool doRestart); +}; + + +QT_END_NAMESPACE + +#endif // QNEARFIELDMANAGER_IOS_P_H diff --git a/src/nfc/qnearfieldtarget_ios.mm b/src/nfc/qnearfieldtarget_ios.mm new file mode 100644 index 00000000..5b280389 --- /dev/null +++ b/src/nfc/qnearfieldtarget_ios.mm @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnearfieldtarget_ios_p.h" + +#import +#import +#import +#import + +QT_BEGIN_NAMESPACE + +void NfcTagDeleter::operator()(void *tag) +{ + [static_cast>(tag) release]; +} + +QNearFieldTargetPrivateImpl:: QNearFieldTargetPrivateImpl(void *tag, QObject *parent) : + QNearFieldTargetPrivate(parent), + nfcTag(tag) +{ + Q_ASSERT(nfcTag); + + QObject::connect(&targetCheckTimer, &QTimer::timeout, this, &QNearFieldTargetPrivateImpl::onTargetCheck); + targetCheckTimer.start(500); +} + +QNearFieldTargetPrivateImpl::~QNearFieldTargetPrivateImpl() +{ +} + +void QNearFieldTargetPrivateImpl::invalidate() +{ + queue.clear(); + nfcTag.reset(); + targetCheckTimer.stop(); +} + +QByteArray QNearFieldTargetPrivateImpl::uid() const +{ + if (!nfcTag) + return QByteArray(); + + if (@available(iOS 13, *)) { + id tag = static_cast>(nfcTag.get()); + id iso7816Tag = tag.asNFCISO7816Tag; + if (iso7816Tag) + return QByteArray::fromNSData(iso7816Tag.identifier); + } + + return QByteArray(); +} + +QNearFieldTarget::Type QNearFieldTargetPrivateImpl::type() const +{ + if (!nfcTag) + return QNearFieldTarget::ProprietaryTag; + + if (@available(iOS 13, *)) { + id tag = static_cast>(nfcTag.get()); + id iso7816Tag = tag.asNFCISO7816Tag; + + if (tag.type != NFCTagTypeISO7816Compatible || iso7816Tag == nil) + return QNearFieldTarget::ProprietaryTag; + + if (iso7816Tag.historicalBytes != nil && iso7816Tag.applicationData == nil) + return QNearFieldTarget::NfcTagType4A; + + if (iso7816Tag.historicalBytes == nil && iso7816Tag.applicationData != nil) + return QNearFieldTarget::NfcTagType4B; + + return QNearFieldTarget::NfcTagType4; + } + + return QNearFieldTarget::ProprietaryTag; +} + +QNearFieldTarget::AccessMethods QNearFieldTargetPrivateImpl::accessMethods() const +{ + if (@available(iOS 13, *)) { + id tag = static_cast>(nfcTag.get()); + if (tag && [tag conformsToProtocol:@protocol(NFCISO7816Tag)]) + return QNearFieldTarget::TagTypeSpecificAccess; + } + + return QNearFieldTarget::UnknownAccess; +} + +int QNearFieldTargetPrivateImpl::maxCommandLength() const +{ + if (accessMethods() & QNearFieldTarget::TagTypeSpecificAccess) + return 0xFEFF; + + return 0; +} + +QNearFieldTarget::RequestId QNearFieldTargetPrivateImpl::sendCommand(const QByteArray &command) +{ + QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate()); + + if (!(accessMethods() & QNearFieldTarget::TagTypeSpecificAccess)) { + reportError(QNearFieldTarget::UnsupportedError, requestId); + return requestId; + } + + queue.enqueue(std::pair(requestId, command)); + + if (!connect()) { + reportError(QNearFieldTarget::TargetOutOfRangeError, requestId); + return requestId; + } + + onExecuteRequest(); + return requestId; +} + +bool QNearFieldTargetPrivateImpl::isAvailable() const +{ + if (@available(iOS 13, *)) { + id tag = static_cast>(nfcTag.get()); + return tag && (!connected || tag.available); + } + + return false; +} + +bool QNearFieldTargetPrivateImpl::connect() +{ + if (connected || requestInProgress) + return true; + + if (!isAvailable()) + return false; + + if (@available(iOS 13, *)) { + requestInProgress = true; + id tag = static_cast>(nfcTag.get()); + NFCTagReaderSession* session = tag.session; + [session connectToTag: tag completionHandler: ^(NSError* error){ + const bool success = error == nil; + QMetaObject::invokeMethod(this, [this, success] { + requestInProgress = false; + if (success) { + connected = true; + onExecuteRequest(); + } else { + invalidate(); + Q_EMIT targetLost(this); + reportError(QNearFieldTarget::ConnectionError, queue.head().first); + } + }); + }]; + return true; + } + + return false; +} + +void QNearFieldTargetPrivateImpl::onTargetCheck() +{ + if (!isAvailable()) { + invalidate(); + Q_EMIT targetLost(this); + } +} + +void QNearFieldTargetPrivateImpl::onExecuteRequest() +{ + if (!nfcTag || requestInProgress || queue.isEmpty()) + return; + + if (@available(iOS 13, *)) { + requestInProgress = true; + const auto request = queue.dequeue(); + const auto tag = static_cast>(nfcTag.get()); + auto* apdu = [[NFCISO7816APDU alloc] initWithData: request.second.toNSData()]; + [tag sendCommandAPDU: apdu completionHandler: ^(NSData* responseData, uint8_t sw1, uint8_t sw2, NSError* error){ + QByteArray recvBuffer = QByteArray::fromNSData(responseData); + recvBuffer += static_cast(sw1); + recvBuffer += static_cast(sw2); + const bool success = error == nil; + const auto requestId = request.first; + QMetaObject::invokeMethod(this, [this, success, requestId, recvBuffer] { + requestInProgress = false; + if (success) { + setResponseForRequest(requestId, recvBuffer, true); + onExecuteRequest(); + } else { + invalidate(); + Q_EMIT targetLost(this); + reportError(QNearFieldTarget::CommandError, requestId); + } + }); + }]; + } +} + +QT_END_NAMESPACE diff --git a/src/nfc/qnearfieldtarget_ios_p.h b/src/nfc/qnearfieldtarget_ios_p.h new file mode 100644 index 00000000..93c7c098 --- /dev/null +++ b/src/nfc/qnearfieldtarget_ios_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNfc module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNEARFIELDTARGET_IOS_P_H +#define QNEARFIELDTARGET_IOS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qnearfieldtarget_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct NfcTagDeleter +{ + void operator()(void *tag); +}; + +class QNearFieldTargetPrivateImpl : public QNearFieldTargetPrivate +{ + Q_OBJECT + +public: + QNearFieldTargetPrivateImpl(void *tag, QObject *parent = nullptr); + ~QNearFieldTargetPrivateImpl() override; + void invalidate(); + + QByteArray uid() const override; + QNearFieldTarget::Type type() const override; + QNearFieldTarget::AccessMethods accessMethods() const override; + + int maxCommandLength() const override; + QNearFieldTarget::RequestId sendCommand(const QByteArray &command) override; + + bool isAvailable() const; + +Q_SIGNALS: + void targetLost(QNearFieldTargetPrivateImpl *target); + +private: + std::unique_ptr nfcTag; + bool connected = false; + QTimer targetCheckTimer; + bool requestInProgress = false; + QQueue> queue; + + bool connect(); + +private Q_SLOTS: + void onTargetCheck(); + void onExecuteRequest(); +}; + +QT_END_NAMESPACE + +#endif // QNEARFIELDTARGET_IOS_P_H -- cgit v1.2.3