blob: 6a93143b2234f6066f8a2716b75067ab87d6f851 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth 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 "qbluetoothservicediscoveryagent.h"
#include "qbluetoothservicediscoveryagent_p.h"
#include "bluez/manager_p.h"
#include "bluez/adapter_p.h"
#include "bluez/device_p.h"
#include "bluez/bluez5_helper_p.h"
#include "bluez/objectmanager_p.h"
#include "bluez/adapter1_bluez5_p.h"
#include <QtCore/QFile>
#include <QtCore/QLibraryInfo>
#include <QtCore/QLoggingCategory>
#include <QtCore/QProcess>
#include <QtDBus/QDBusPendingCallWatcher>
#include <QtConcurrent/QtConcurrentRun>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive),
mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false),
q_ptr(qp)
{
if (isBluez5()) {
managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
QStringLiteral("org.bluez"), QStringLiteral("/"),
QDBusConnection::systemBus());
qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
} else {
qRegisterMetaType<ServiceMap>();
qDBusRegisterMetaType<ServiceMap>();
manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"),
QDBusConnection::systemBus());
}
}
QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
{
delete device;
delete manager;
delete managerBluez5;
delete adapter;
}
void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
qCDebug(QT_BT_BLUEZ) << "Discovery on: " << address.toString() << "Mode:" << DiscoveryMode();
if (managerBluez5) {
startBluez5(address);
return;
}
QDBusPendingReply<QDBusObjectPath> reply;
if (m_deviceAdapterAddress.isNull())
reply = manager->DefaultAdapter();
else
reply = manager->FindAdapter(m_deviceAdapterAddress.toString());
reply.waitForFinished();
if (reply.isError()) {
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to find appointed local adapter");
emit q->error(error);
_q_serviceDiscoveryFinished();
return;
}
adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"), reply.value().path(),
QDBusConnection::systemBus());
if (m_deviceAdapterAddress.isNull()) {
QDBusPendingReply<QVariantMap> reply = adapter->GetProperties();
reply.waitForFinished();
if (!reply.isError()) {
const QBluetoothAddress path_address(reply.value().value(QStringLiteral("Address")).toString());
m_deviceAdapterAddress = path_address;
}
}
QDBusPendingReply<QDBusObjectPath> deviceObjectPath = adapter->FindDevice(address.toString());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(deviceObjectPath, q);
watcher->setProperty("_q_BTaddress", QVariant::fromValue(address));
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
q, [this](QDBusPendingCallWatcher *watcher){
this->_q_foundDevice(watcher);
});
}
// Bluez 5
void QBluetoothServiceDiscoveryAgentPrivate::startBluez5(const QBluetoothAddress &address)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
if (foundHostAdapterPath.isEmpty()) {
// check that we match adapter addresses or use first if it wasn't specified
bool ok = false;
foundHostAdapterPath = findAdapterForAddress(m_deviceAdapterAddress, &ok);
if (!ok) {
discoveredDevices.clear();
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot access adapter during service discovery");
emit q->error(error);
_q_serviceDiscoveryFinished();
return;
}
if (foundHostAdapterPath.isEmpty()) {
// Cannot find a local adapter
// Abort any outstanding discoveries
discoveredDevices.clear();
error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot find local Bluetooth adapter");
emit q->error(error);
_q_serviceDiscoveryFinished();
return;
}
}
// ensure we didn't go offline yet
OrgBluezAdapter1Interface adapter(QStringLiteral("org.bluez"),
foundHostAdapterPath, QDBusConnection::systemBus());
if (!adapter.powered()) {
discoveredDevices.clear();
error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Local device is powered off");
emit q->error(error);
_q_serviceDiscoveryFinished();
return;
}
if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
performMinimalServiceDiscovery(address);
} else {
runExternalSdpScan(address, QBluetoothAddress(adapter.address()));
}
}
/* Bluez 5
* src/tools/sdpscanner performs an SDP scan. This is
* done out-of-process to avoid license issues. At this stage Bluez uses GPLv2.
*/
void QBluetoothServiceDiscoveryAgentPrivate::runExternalSdpScan(
const QBluetoothAddress &remoteAddress, const QBluetoothAddress &localAddress)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
if (!sdpScannerProcess) {
const QString binPath = QLibraryInfo::location(QLibraryInfo::BinariesPath);
QFileInfo fileInfo(binPath, QStringLiteral("sdpscanner"));
if (!fileInfo.exists() || !fileInfo.isExecutable()) {
_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
QBluetoothServiceDiscoveryAgent::tr("Unable to find sdpscanner"),
QStringList());
qCWarning(QT_BT_BLUEZ) << "Cannot find sdpscanner:"
<< fileInfo.canonicalFilePath();
return;
}
sdpScannerProcess = new QProcess(q);
sdpScannerProcess->setReadChannel(QProcess::StandardOutput);
if (QT_BT_BLUEZ().isDebugEnabled())
sdpScannerProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
sdpScannerProcess->setProgram(fileInfo.canonicalFilePath());
q->connect(sdpScannerProcess,
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
q, [this](int exitCode, QProcess::ExitStatus status){
this->_q_sdpScannerDone(exitCode, status);
});
}
QStringList arguments;
arguments << remoteAddress.toString() << localAddress.toString();
// No filter implies PUBLIC_BROWSE_GROUP based SDP scan
if (!uuidFilter.isEmpty()) {
arguments << QLatin1String("-u"); // cmd line option for list of uuids
for (const QBluetoothUuid& uuid : qAsConst(uuidFilter))
arguments << uuid.toString();
}
sdpScannerProcess->setArguments(arguments);
sdpScannerProcess->start();
}
// Bluez 5
void QBluetoothServiceDiscoveryAgentPrivate::_q_sdpScannerDone(int exitCode, QProcess::ExitStatus status)
{
if (status != QProcess::NormalExit || exitCode != 0) {
qCWarning(QT_BT_BLUEZ) << "SDP scan failure" << status << exitCode;
if (singleDevice) {
_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::InputOutputError,
QBluetoothServiceDiscoveryAgent::tr("Unable to perform SDP scan"),
QStringList());
} else {
// go to next device
_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), QStringList());
}
return;
}
QStringList xmlRecords;
const QByteArray output = sdpScannerProcess->readAllStandardOutput();
const QString decodedData = QString::fromUtf8(QByteArray::fromBase64(output));
// split the various xml docs up
int next;
int start = decodedData.indexOf(QStringLiteral("<?xml"), 0);
if (start != -1) {
do {
next = decodedData.indexOf(QStringLiteral("<?xml"), start + 1);
if (next != -1)
xmlRecords.append(decodedData.mid(start, next-start));
else
xmlRecords.append(decodedData.mid(start, decodedData.size() - start));
start = next;
} while ( start != -1);
}
_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::NoError, QString(), xmlRecords);
}
// Bluez 5
void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode,
const QString &errorDescription,
const QStringList &xmlRecords)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
if (errorCode != QBluetoothServiceDiscoveryAgent::NoError) {
qCWarning(QT_BT_BLUEZ) << "SDP search failed for"
<< (!discoveredDevices.isEmpty()
? discoveredDevices.at(0).address().toString()
: QStringLiteral("<Unknown>"));
// We have an error which we need to indicate and stop further processing
discoveredDevices.clear();
error = errorCode;
errorString = errorDescription;
emit q->error(error);
} else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) {
for (const QString &record : xmlRecords) {
QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
//apply uuidFilter
if (!uuidFilter.isEmpty()) {
bool serviceNameMatched = uuidFilter.contains(serviceInfo.serviceUuid());
bool serviceClassMatched = false;
const QList<QBluetoothUuid> serviceClassUuids
= serviceInfo.serviceClassUuids();
for (const QBluetoothUuid &id : serviceClassUuids) {
if (uuidFilter.contains(id)) {
serviceClassMatched = true;
break;
}
}
if (!serviceNameMatched && !serviceClassMatched)
continue;
}
if (!serviceInfo.isValid())
continue;
// Bluez sdpscanner declares custom uuids into the service class uuid list.
// Let's move a potential custom uuid from QBluetoothServiceInfo::serviceClassUuids()
// to QBluetoothServiceInfo::serviceUuid(). If there is more than one, just move the first uuid
const QList<QBluetoothUuid> serviceClassUuids = serviceInfo.serviceClassUuids();
for (const QBluetoothUuid &id : serviceClassUuids) {
if (id.minimumSize() == 16) {
serviceInfo.setServiceUuid(id);
serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
QBluetoothServiceInfo::Sequence modSeq =
serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
modSeq.removeOne(QVariant::fromValue(id));
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, modSeq);
break;
}
}
if (!isDuplicatedService(serviceInfo)) {
discoveredServices.append(serviceInfo);
qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
<< serviceInfo.serviceName() << serviceInfo.serviceUuid()
<< ">>>" << serviceInfo.serviceClassUuids();
emit q->serviceDiscovered(serviceInfo);
}
}
}
_q_serviceDiscoveryFinished();
}
void QBluetoothServiceDiscoveryAgentPrivate::stop()
{
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Stop called";
if (device) {
//we are waiting for _q_discoveredServices() to be called
// adapter is already 0
QDBusPendingReply<> reply = device->CancelDiscovery();
reply.waitForFinished();
device->deleteLater();
device = nullptr;
Q_ASSERT(!adapter);
} else if (adapter) {
//we are waiting for _q_createdDevice() to be called
adapter->deleteLater();
adapter = nullptr;
Q_ASSERT(!device);
}
discoveredDevices.clear();
setDiscoveryState(Inactive);
// must happen after discoveredDevices.clear() above to avoid retrigger of next scan
// while waitForFinished() is waiting
if (sdpScannerProcess) { // Bluez 5
if (sdpScannerProcess->state() != QProcess::NotRunning) {
sdpScannerProcess->kill();
sdpScannerProcess->waitForFinished();
}
}
Q_Q(QBluetoothServiceDiscoveryAgent);
emit q->canceled();
}
void QBluetoothServiceDiscoveryAgentPrivate::_q_foundDevice(QDBusPendingCallWatcher *watcher)
{
if (!adapter) {
watcher->deleteLater();
return;
}
Q_Q(QBluetoothServiceDiscoveryAgent);
const QBluetoothAddress &address = watcher->property("_q_BTaddress").value<QBluetoothAddress>();
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "found" << address.toString();
QDBusPendingReply<QDBusObjectPath> deviceObjectPath = *watcher;
watcher->deleteLater();
if (deviceObjectPath.isError()) {
if (deviceObjectPath.error().name() != QStringLiteral("org.bluez.Error.DoesNotExist")) {
qCDebug(QT_BT_BLUEZ) << "Find device failed Error: " << error << deviceObjectPath.error().name();
delete adapter;
adapter = nullptr;
if (singleDevice) {
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
emit q->error(error);
}
_q_serviceDiscoveryFinished();
return;
}
deviceObjectPath = adapter->CreateDevice(address.toString());
watcher = new QDBusPendingCallWatcher(deviceObjectPath, q);
watcher->setProperty("_q_BTaddress", QVariant::fromValue(address));
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
q, [this](QDBusPendingCallWatcher *watcher){
this->_q_createdDevice(watcher);
});
return;
}
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "path" << deviceObjectPath.value().path();
discoverServices(deviceObjectPath.value().path());
}
void QBluetoothServiceDiscoveryAgentPrivate::_q_createdDevice(QDBusPendingCallWatcher *watcher)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
if (!adapter) {
watcher->deleteLater();
return;
}
const QBluetoothAddress &address = watcher->property("_q_BTaddress").value<QBluetoothAddress>();
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "created" << address.toString();
QDBusPendingReply<QDBusObjectPath> deviceObjectPath = *watcher;
watcher->deleteLater();
if (deviceObjectPath.isError()) {
if (deviceObjectPath.error().name() != QLatin1String("org.bluez.Error.AlreadyExists")) {
qCDebug(QT_BT_BLUEZ) << "Create device failed Error: " << error << deviceObjectPath.error().name();
delete adapter;
adapter = nullptr;
if (singleDevice) {
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
emit q->error(error);
}
_q_serviceDiscoveryFinished();
return;
}
}
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "path" << deviceObjectPath.value().path();
discoverServices(deviceObjectPath.value().path());
}
void QBluetoothServiceDiscoveryAgentPrivate::discoverServices(const QString &deviceObjectPath)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
device = new OrgBluezDeviceInterface(QStringLiteral("org.bluez"),
deviceObjectPath,
QDBusConnection::systemBus());
delete adapter;
adapter = nullptr;
QVariantMap deviceProperties;
QString classType;
QDBusPendingReply<QVariantMap> deviceReply = device->GetProperties();
deviceReply.waitForFinished();
if (!deviceReply.isError()) {
deviceProperties = deviceReply.value();
classType = deviceProperties.value(QStringLiteral("Class")).toString();
}
/*
* Low Energy services in bluez are represented as the list of the paths that can be
* accessed with org.bluez.Characteristic
*/
//QDBusArgument services = v.value(QLatin1String("Services")).value<QDBusArgument>();
/*
* Bluez v4.1 does not have extra bit which gives information if device is Bluetooth
* Low Energy device and the way to discover it is with Class property of the Bluetooth device.
* Low Energy devices do not have property Class.
* In case we have LE device finish service discovery; otherwise search for regular services.
*/
if (classType.isEmpty()) { //is BLE device or device properties above not retrievable
qCDebug(QT_BT_BLUEZ) << "Discovered BLE-only device. Normal service discovery skipped.";
delete device;
device = nullptr;
const QStringList deviceUuids = deviceProperties.value(QStringLiteral("UUIDs")).toStringList();
for (int i = 0; i < deviceUuids.size(); i++) {
QString b = deviceUuids.at(i);
b = b.remove(QLatin1Char('{')).remove(QLatin1Char('}'));
const QBluetoothUuid uuid(b);
qCDebug(QT_BT_BLUEZ) << "Discovered service" << uuid << uuidFilter.size();
QBluetoothServiceInfo service;
service.setDevice(discoveredDevices.at(0));
bool ok = false;
quint16 serviceClass = uuid.toUInt16(&ok);
if (ok)
service.setServiceName(QBluetoothUuid::serviceClassToString(
static_cast<QBluetoothUuid::ServiceClassUuid>(serviceClass)));
QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(uuid);
service.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
QBluetoothServiceInfo::Sequence protocolDescriptorList;
{
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
protocolDescriptorList.append(QVariant::fromValue(protocol));
}
{
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
protocolDescriptorList.append(QVariant::fromValue(protocol));
}
service.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
if (uuidFilter.isEmpty())
emit q->serviceDiscovered(service);
else {
for (int j = 0; j < uuidFilter.size(); j++) {
if (uuidFilter.at(j) == uuid)
emit q->serviceDiscovered(service);
}
}
}
if (singleDevice && deviceReply.isError()) {
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device");
emit q->error(error);
}
_q_serviceDiscoveryFinished();
} else {
QString pattern;
for (const QBluetoothUuid &uuid : qAsConst(uuidFilter))
pattern += uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')) + QLatin1Char(' ');
pattern = pattern.trimmed();
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Discover restrictions:" << pattern;
QDBusPendingReply<ServiceMap> discoverReply = device->DiscoverServices(pattern);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(discoverReply, q);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
q, [this](QDBusPendingCallWatcher *watcher){
this->_q_discoveredServices(watcher);
});
}
}
// Bluez 4
void QBluetoothServiceDiscoveryAgentPrivate::_q_discoveredServices(QDBusPendingCallWatcher *watcher)
{
if (!device) {
watcher->deleteLater();
return;
}
qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
Q_Q(QBluetoothServiceDiscoveryAgent);
QDBusPendingReply<ServiceMap> reply = *watcher;
if (reply.isError()) {
qCDebug(QT_BT_BLUEZ) << "discoveredServices error: " << error << reply.error().message();
watcher->deleteLater();
if (singleDevice) {
error = QBluetoothServiceDiscoveryAgent::UnknownError;
errorString = reply.error().message();
emit q->error(error);
}
delete device;
device = nullptr;
_q_serviceDiscoveryFinished();
return;
}
const ServiceMap map = reply.value();
qCDebug(QT_BT_BLUEZ) << "Parsing xml" << discoveredDevices.at(0).address().toString() << discoveredDevices.count() << map.count();
for (const QString &record : map) {
QBluetoothServiceInfo serviceInfo = parseServiceXml(record);
if (!serviceInfo.isValid())
continue;
// Don't need to apply uuidFilter because Bluez 4 applies
// search pattern during DiscoverServices() call
Q_Q(QBluetoothServiceDiscoveryAgent);
// Some service uuids are unknown to Bluez. In such cases we fall back
// to our own naming resolution.
if (serviceInfo.serviceName().isEmpty()
&& !serviceInfo.serviceClassUuids().isEmpty()) {
const QList<QBluetoothUuid> classUuids = serviceInfo.serviceClassUuids();
for (const QBluetoothUuid &classUuid : classUuids) {
bool ok = false;
QBluetoothUuid::ServiceClassUuid clsId
= static_cast<QBluetoothUuid::ServiceClassUuid>(classUuid.toUInt16(&ok));
if (ok) {
serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
break;
}
}
}
if (!isDuplicatedService(serviceInfo)) {
discoveredServices.append(serviceInfo);
qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
<< serviceInfo.serviceName();
emit q->serviceDiscovered(serviceInfo);
}
// could stop discovery, check for state
if (discoveryState() == Inactive)
qCDebug(QT_BT_BLUEZ) << "Exit discovery after stop";
}
watcher->deleteLater();
delete device;
device = nullptr;
_q_serviceDiscoveryFinished();
}
QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(
const QString& xmlRecord)
{
QXmlStreamReader xml(xmlRecord);
QBluetoothServiceInfo serviceInfo;
serviceInfo.setDevice(discoveredDevices.at(0));
while (!xml.atEnd()) {
xml.readNext();
if (xml.tokenType() == QXmlStreamReader::StartElement &&
xml.name() == QLatin1String("attribute")) {
quint16 attributeId =
xml.attributes().value(QLatin1String("id")).toString().toUShort(nullptr, 0);
if (xml.readNextStartElement()) {
const QVariant value = readAttributeValue(xml);
serviceInfo.setAttribute(attributeId, value);
}
}
}
return serviceInfo;
}
// Bluez 5
void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress)
{
if (foundHostAdapterPath.isEmpty()) {
_q_serviceDiscoveryFinished();
return;
}
Q_Q(QBluetoothServiceDiscoveryAgent);
QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
reply.waitForFinished();
if (reply.isError()) {
if (singleDevice) {
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = reply.error().message();
emit q->error(error);
}
_q_serviceDiscoveryFinished();
return;
}
QStringList uuidStrings;
ManagedObjectList managedObjectList = reply.value();
for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
const InterfaceList &ifaceList = it.value();
for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
const QString &iface = jt.key();
const QVariantMap &ifaceValues = jt.value();
if (iface == QStringLiteral("org.bluez.Device1")) {
if (deviceAddress.toString() == ifaceValues.value(QStringLiteral("Address")).toString()) {
uuidStrings = ifaceValues.value(QStringLiteral("UUIDs")).toStringList();
break;
}
}
}
if (!uuidStrings.isEmpty())
break;
}
if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) {
qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString();
// nothing found -> go to next uuid
_q_serviceDiscoveryFinished();
return;
}
qCDebug(QT_BT_BLUEZ) << "Minimal uuid list for" << deviceAddress.toString() << uuidStrings;
QBluetoothUuid uuid;
for (int i = 0; i < uuidStrings.count(); i++) {
uuid = QBluetoothUuid(uuidStrings.at(i));
if (uuid.isNull())
continue;
//apply uuidFilter
if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid))
continue;
QBluetoothServiceInfo serviceInfo;
serviceInfo.setDevice(discoveredDevices.at(0));
if (uuid.minimumSize() == 16) { // not derived from Bluetooth Base UUID
serviceInfo.setServiceUuid(uuid);
serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
} else {
// set uuid as service class id
QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(uuid);
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
QBluetoothUuid::ServiceClassUuid clsId
= static_cast<QBluetoothUuid::ServiceClassUuid>(uuid.data1 & 0xffff);
serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
}
QBluetoothServiceInfo::Sequence protocolDescriptorList;
{
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
protocolDescriptorList.append(QVariant::fromValue(protocol));
}
{
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Att));
protocolDescriptorList.append(QVariant::fromValue(protocol));
}
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
//don't include the service if we already discovered it before
if (!isDuplicatedService(serviceInfo)) {
discoveredServices << serviceInfo;
qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
<< serviceInfo.serviceName();
emit q->serviceDiscovered(serviceInfo);
}
}
_q_serviceDiscoveryFinished();
}
QVariant QBluetoothServiceDiscoveryAgentPrivate::readAttributeValue(QXmlStreamReader &xml)
{
if (xml.name() == QLatin1String("boolean")) {
const QString value = xml.attributes().value(QStringLiteral("value")).toString();
xml.skipCurrentElement();
return value == QLatin1String("true");
} else if (xml.name() == QLatin1String("uint8")) {
quint8 value = xml.attributes().value(QStringLiteral("value")).toString().toUShort(nullptr, 0);
xml.skipCurrentElement();
return value;
} else if (xml.name() == QLatin1String("uint16")) {
quint16 value = xml.attributes().value(QStringLiteral("value")).toString().toUShort(nullptr, 0);
xml.skipCurrentElement();
return value;
} else if (xml.name() == QLatin1String("uint32")) {
quint32 value = xml.attributes().value(QStringLiteral("value")).toString().toUInt(nullptr, 0);
xml.skipCurrentElement();
return value;
} else if (xml.name() == QLatin1String("uint64")) {
quint64 value = xml.attributes().value(QStringLiteral("value")).toString().toULongLong(nullptr, 0);
xml.skipCurrentElement();
return value;
} else if (xml.name() == QLatin1String("uuid")) {
QBluetoothUuid uuid;
const QString value = xml.attributes().value(QStringLiteral("value")).toString();
if (value.startsWith(QStringLiteral("0x"))) {
if (value.length() == 6) {
quint16 v = value.toUShort(nullptr, 0);
uuid = QBluetoothUuid(v);
} else if (value.length() == 10) {
quint32 v = value.toUInt(nullptr, 0);
uuid = QBluetoothUuid(v);
}
} else {
uuid = QBluetoothUuid(value);
}
xml.skipCurrentElement();
return QVariant::fromValue(uuid);
} else if (xml.name() == QLatin1String("text") || xml.name() == QLatin1String("url")) {
QString value = xml.attributes().value(QStringLiteral("value")).toString();
if (xml.attributes().value(QStringLiteral("encoding")) == QLatin1String("hex"))
value = QString::fromUtf8(QByteArray::fromHex(value.toLatin1()));
xml.skipCurrentElement();
return value;
} else if (xml.name() == QLatin1String("sequence")) {
QBluetoothServiceInfo::Sequence sequence;
while (xml.readNextStartElement()) {
QVariant value = readAttributeValue(xml);
sequence.append(value);
}
return QVariant::fromValue<QBluetoothServiceInfo::Sequence>(sequence);
} else {
qCWarning(QT_BT_BLUEZ) << "unknown attribute type"
<< xml.name().toString()
<< xml.attributes().value(QStringLiteral("value")).toString();
xml.skipCurrentElement();
return QVariant();
}
}
QT_END_NAMESPACE