/*************************************************************************** ** ** Copyright (C) 2016 BlackBerry Limited. All rights reserved. ** Copyright (C) 2016 BasysKom GmbH. ** 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_NEARD_P_H #define QNEARFIELDTARGET_NEARD_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 #include #include #include #include #include #include "neard/neard_helper_p.h" #include "neard/dbusproperties_p.h" #include "neard/dbusobjectmanager_p.h" #include "neard/tag_p.h" #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_NFC_NEARD) template class NearFieldTarget : public T { public: NearFieldTarget(QObject *parent, QDBusObjectPath interfacePath) : T(parent), m_tagPath(interfacePath), m_readRequested(false) { m_readErrorTimer.setSingleShot(true); m_recordPathsCollectedTimer.setSingleShot(true); m_delayedWriteTimer.setSingleShot(true); qCDebug(QT_NFC_NEARD) << "tag found at path" << interfacePath.path(); m_dbusProperties = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.neard"), interfacePath.path(), QDBusConnection::systemBus(), this); if (!m_dbusProperties->isValid()) { qCWarning(QT_NFC_NEARD) << "Could not connect to dbus property interface at path" << interfacePath.path(); return; } QDBusPendingReply reply = m_dbusProperties->GetAll(QStringLiteral("org.neard.Tag")); reply.waitForFinished(); if (reply.isError()) { qCWarning(QT_NFC_NEARD) << "Could not get properties of org.neard.Tag dbus interface"; return; } const QString &type = reply.value().value(QStringLiteral("Type")).toString(); m_type = QNearFieldTarget::ProprietaryTag; if (type == QStringLiteral("Type 1")) m_type = QNearFieldTarget::NfcTagType1; else if (type == QStringLiteral("Type 2")) m_type = QNearFieldTarget::NfcTagType2; else if (type == QStringLiteral("Type 3")) m_type = QNearFieldTarget::NfcTagType3; else if (type == QStringLiteral("Type 4")) m_type = QNearFieldTarget::NfcTagType4; qCDebug(QT_NFC_NEARD) << "tag type" << type; QObject::connect(&m_recordPathsCollectedTimer, &QTimer::timeout, this, &NearFieldTarget::createNdefMessage); QObject::connect(&m_readErrorTimer, &QTimer::timeout, this, &NearFieldTarget::handleReadError); QObject::connect(&m_delayedWriteTimer, &QTimer::timeout, this, &NearFieldTarget::handleWriteRequest); QObject::connect(NeardHelper::instance(), &NeardHelper::recordFound, this, &NearFieldTarget::handleRecordFound); } ~NearFieldTarget() { } bool isValid() { return m_dbusProperties->isValid() && NeardHelper::instance()->dbusObjectManager()->isValid(); } QByteArray uid() const { return QByteArray(); // TODO figure out a workaround because neard does not offer // this property } QNearFieldTarget::Type type() const { return m_type; } QNearFieldTarget::AccessMethods accessMethods() const { return QNearFieldTarget::NdefAccess; } bool hasNdefMessage() { return !m_recordPaths.isEmpty(); } QNearFieldTarget::RequestId readNdefMessages() { if (isValid()) { // if the user calls readNdefMessages before the previous request has been completed // return the current request id. if (m_currentReadRequestId.isValid()) return m_currentReadRequestId; QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate()); // save the id so it can be passed along with requestCompleted m_currentReadRequestId = requestId; // since the triggering of interfaceAdded will ultimately lead to createNdefMessage being called // we need to make sure that ndefMessagesRead will only be triggered when readNdefMessages has // been called before. In case readNdefMessages is called again after that we can directly call // call createNdefMessage. m_readRequested = true; if (hasNdefMessage()) createNdefMessage(); else m_readErrorTimer.start(1000); return requestId; } else { return QNearFieldTarget::RequestId(); } } QNearFieldTarget::RequestId sendCommand(const QByteArray &command) { Q_UNUSED(command); return QNearFieldTarget::RequestId(); } QNearFieldTarget::RequestId sendCommands(const QList &commands) { Q_UNUSED(commands); return QNearFieldTarget::RequestId(); } QNearFieldTarget::RequestId writeNdefMessages(const QList &messages) { // disabling write due to neard crash (see QTBUG-43802) qWarning("QNearFieldTarget::WriteNdefMessages() disabled. See QTBUG-43802\n"); return QNearFieldTarget::RequestId(); // return old request id when previous write request hasn't completed if (m_currentWriteRequestId.isValid()) return m_currentReadRequestId; qCDebug(QT_NFC_NEARD) << "writing messages"; if (messages.isEmpty() || messages.first().isEmpty()) { qCWarning(QT_NFC_NEARD) << "No record specified"; return QNearFieldTarget::RequestId(); } if (messages.count() > 1 || messages.first().count() > 1) { // neard only supports one ndef record per tag qCWarning(QT_NFC_NEARD) << "Writing of only one NDEF record and message is supported"; return QNearFieldTarget::RequestId(); } QNdefRecord record = messages.first().first(); if (record.typeNameFormat() == QNdefRecord::NfcRtd) { m_currentWriteRequestData.clear(); if (record.isRecordType()) { m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("URI")); QNdefNfcUriRecord uriRecord = static_cast(record); m_currentWriteRequestData.insert(QStringLiteral("URI"), uriRecord.uri().toString()); } else if (record.isRecordType()) { m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("SmartPoster")); QNdefNfcSmartPosterRecord spRecord = static_cast(record); m_currentWriteRequestData.insert(QStringLiteral("URI"), spRecord.uri().toString()); // Currently neard only supports the uri property for writing } else if (record.isRecordType()) { m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("Text")); QNdefNfcTextRecord textRecord = static_cast(record); m_currentWriteRequestData.insert(QStringLiteral("Representation"), textRecord.text()); m_currentWriteRequestData.insert(QStringLiteral("Encoding"), textRecord.encoding() == QNdefNfcTextRecord::Utf8 ? QStringLiteral("UTF-8") : QStringLiteral("UTF-16") ); m_currentWriteRequestData.insert(QStringLiteral("Language"), textRecord.locale()); } else { qCWarning(QT_NFC_NEARD) << "Record type not supported for writing"; return QNearFieldTarget::RequestId(); } m_currentWriteRequestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate()); // trigger delayed write m_delayedWriteTimer.start(100); return m_currentWriteRequestId; } return QNearFieldTarget::RequestId(); } private: QNdefRecord readRecord(const QDBusObjectPath &path) { qCDebug(QT_NFC_NEARD) << "reading record for path" << path.path(); OrgFreedesktopDBusPropertiesInterface recordInterface(QStringLiteral("org.neard"), path.path(), QDBusConnection::systemBus()); if (!recordInterface.isValid()) return QNdefRecord(); QDBusPendingReply reply = recordInterface.GetAll(QStringLiteral("org.neard.Record")); reply.waitForFinished(); if (reply.isError()) return QNdefRecord(); const QString &value = reply.value().value(QStringLiteral("Representation")).toString(); const QString &locale = reply.value().value(QStringLiteral("Language")).toString(); const QString &encoding = reply.value().value(QStringLiteral("Encoding")).toString(); const QString &uri = reply.value().value(QStringLiteral("URI")).toString(); // const QString &mime = reply.value().value(QStringLiteral("MIME")).toString(); // const QString &arr = reply.value().value(QStringLiteral("ARR")).toString(); const QString type = reply.value().value(QStringLiteral("Type")).toString(); if (type == QStringLiteral("Text")) { QNdefNfcTextRecord textRecord; textRecord.setText(value); textRecord.setLocale(locale); textRecord.setEncoding((encoding == QStringLiteral("UTF-8")) ? QNdefNfcTextRecord::Utf8 : QNdefNfcTextRecord::Utf16); return textRecord; } else if (type == QStringLiteral("SmartPoster")) { QNdefNfcSmartPosterRecord spRecord; if (!value.isEmpty()) { spRecord.addTitle(value, locale, (encoding == QStringLiteral("UTF-8")) ? QNdefNfcTextRecord::Utf8 : QNdefNfcTextRecord::Utf16); } if (!uri.isEmpty()) spRecord.setUri(QUrl(uri)); const QString &action = reply.value().value(QStringLiteral("Action")).toString(); if (!action.isEmpty()) { if (action == QStringLiteral("Do")) spRecord.setAction(QNdefNfcSmartPosterRecord::DoAction); else if (action == QStringLiteral("Save")) spRecord.setAction(QNdefNfcSmartPosterRecord::SaveAction); else if (action == QStringLiteral("Edit")) spRecord.setAction(QNdefNfcSmartPosterRecord::EditAction); } if (reply.value().contains(QStringLiteral("Size"))) { uint size = reply.value().value(QStringLiteral("Size")).toUInt(); spRecord.setSize(size); } const QString &mimeType = reply.value().value(QStringLiteral("MIMEType")).toString(); if (!mimeType.isEmpty()) { spRecord.setTypeInfo(mimeType.toUtf8()); } return spRecord; } else if (type == QStringLiteral("URI")) { QNdefNfcUriRecord uriRecord; uriRecord.setUri(QUrl(uri)); return uriRecord; } else if (type == QStringLiteral("MIME")) { } else if (type == QStringLiteral("AAR")) { } return QNdefRecord(); } void handleRecordFound(const QDBusObjectPath &path) { m_recordPaths.append(path); // FIXME: this timer only exists because neard doesn't currently supply enough // information to let us know when all record interfaces have been added or // how many records are actually contained on a tag. We assume that when no // signal has been received for 100ms all record interfaces have been added. m_recordPathsCollectedTimer.start(100); // as soon as record paths have been added we can handle errors without the timer. m_readErrorTimer.stop(); } void createNdefMessage() { if (m_readRequested) { qCDebug(QT_NFC_NEARD) << "creating Ndef message, reading" << m_recordPaths.length() << "record paths"; QNdefMessage newNdefMessage; for (const QDBusObjectPath &recordPath : qAsConst(m_recordPaths)) newNdefMessage.append(readRecord(recordPath)); if (!newNdefMessage.isEmpty()) { QMetaObject::invokeMethod(this, "ndefMessageRead", Qt::QueuedConnection, Q_ARG(QNdefMessage, newNdefMessage)); // the request id in requestCompleted has to match the one created in readNdefMessages QMetaObject::invokeMethod(this, [this]() { Q_EMIT this->requestCompleted(this->m_currentReadRequestId); }, Qt::QueuedConnection); } else { this->reportError(QNearFieldTarget::UnknownError, m_currentReadRequestId); } m_readRequested = false; // invalidate the current request id m_currentReadRequestId = QNearFieldTarget::RequestId(0); } } void handleReadError() { emit QNearFieldTarget::error(QNearFieldTarget::UnknownError, m_currentReadRequestId); m_currentReadRequestId = QNearFieldTarget::RequestId(0); } void handleWriteRequest() { OrgNeardTagInterface tagInterface(QStringLiteral("org.neard"), m_tagPath.path(), QDBusConnection::systemBus()); if (!tagInterface.isValid()) { qCWarning(QT_NFC_NEARD) << "tag interface invalid"; } else { QDBusPendingReply<> reply; reply = tagInterface.Write(m_currentWriteRequestData); reply.waitForFinished(); if (reply.isError()) { qCWarning(QT_NFC_NEARD) << "Error writing to NFC tag" << reply.error(); this->reportError(QNearFieldTarget::UnknownError, m_currentWriteRequestId); } QMetaObject::invokeMethod(this, "ndefMessagesWritten", Qt::QueuedConnection); QMetaObject::invokeMethod(this, [this]() { Q_EMIT this->requestCompleted(this->m_currentWriteRequestId); }, Qt::QueuedConnection); } // invalidate current write request m_currentWriteRequestId = QNearFieldTarget::RequestId(0); } protected: QDBusObjectPath m_tagPath; OrgFreedesktopDBusPropertiesInterface *m_dbusProperties; QList m_recordPaths; QTimer m_recordPathsCollectedTimer; QTimer m_readErrorTimer; QTimer m_delayedWriteTimer; QNearFieldTarget::Type m_type; bool m_readRequested; QNearFieldTarget::RequestId m_currentReadRequestId; QNearFieldTarget::RequestId m_currentWriteRequestId; QVariantMap m_currentWriteRequestData; }; QT_END_NAMESPACE #endif // QNEARFIELDTARGET_NEARD_P_H