blob: 9844d99e17ac670021b0863aaadac6bd2cf12853 [file] [log] [blame]
/***************************************************************************
**
** 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 <QDBusObjectPath>
#include <QDBusVariant>
#include <qnearfieldtarget.h>
#include <qnearfieldtarget_p.h>
#include <qndefrecord.h>
#include <qndefmessage.h>
#include "neard/neard_helper_p.h"
#include "neard/dbusproperties_p.h"
#include "neard/dbusobjectmanager_p.h"
#include "neard/tag_p.h"
#include <qndefnfctextrecord.h>
#include <qndefnfcsmartposterrecord.h>
#include <qndefnfcurirecord.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_NFC_NEARD)
template <typename T>
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<QVariantMap> 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<QByteArray> &commands)
{
Q_UNUSED(commands);
return QNearFieldTarget::RequestId();
}
QNearFieldTarget::RequestId writeNdefMessages(const QList<QNdefMessage> &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<QNdefNfcUriRecord>()) {
m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("URI"));
QNdefNfcUriRecord uriRecord = static_cast<QNdefNfcUriRecord>(record);
m_currentWriteRequestData.insert(QStringLiteral("URI"), uriRecord.uri().toString());
} else if (record.isRecordType<QNdefNfcSmartPosterRecord>()) {
m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("SmartPoster"));
QNdefNfcSmartPosterRecord spRecord = static_cast<QNdefNfcSmartPosterRecord>(record);
m_currentWriteRequestData.insert(QStringLiteral("URI"), spRecord.uri().toString());
// Currently neard only supports the uri property for writing
} else if (record.isRecordType<QNdefNfcTextRecord>()) {
m_currentWriteRequestData.insert(QStringLiteral("Type"), QStringLiteral("Text"));
QNdefNfcTextRecord textRecord = static_cast<QNdefNfcTextRecord>(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<QVariantMap> 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<QDBusObjectPath> 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