1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtCore/QLoggingCategory>
41#include "qbluetoothdevicediscoveryagent.h"
42#include "qbluetoothdevicediscoveryagent_p.h"
43#include "qbluetoothaddress.h"
44#include "qbluetoothuuid.h"
45
46#include "bluez/manager_p.h"
47#include "bluez/adapter_p.h"
48#include "bluez/device_p.h"
49#include "bluez/bluez5_helper_p.h"
50#include "bluez/objectmanager_p.h"
51#include "bluez/adapter1_bluez5_p.h"
52#include "bluez/device1_bluez5_p.h"
53#include "bluez/properties_p.h"
54#include "bluez/bluetoothmanagement_p.h"
55
56QT_BEGIN_NAMESPACE
57
58Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
59
60QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
61 const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) :
62 lastError(QBluetoothDeviceDiscoveryAgent::NoError),
63 m_adapterAddress(deviceAdapter),
64 pendingCancel(false),
65 pendingStart(false),
66 useExtendedDiscovery(false),
67 lowEnergySearchTimeout(-1), // remains -1 on BlueZ 4 -> timeout not supported
68 q_ptr(parent)
69{
70 if (isBluez5()) {
71 lowEnergySearchTimeout = 20000;
72 managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface(
73 QStringLiteral("org.bluez"),
74 QStringLiteral("/"),
75 QDBusConnection::systemBus(), parent);
76 QObject::connect(managerBluez5,
77 &OrgFreedesktopDBusObjectManagerInterface::InterfacesAdded,
78 q_ptr,
79 [this](const QDBusObjectPath &objectPath, InterfaceList interfacesAndProperties) {
80 this->_q_InterfacesAdded(objectPath, interfacesAndProperties);
81 });
82
83 // start private address monitoring
84 BluetoothManagement::instance();
85 } else {
86 manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"),
87 QDBusConnection::systemBus(), parent);
88 QObject::connect(&extendedDiscoveryTimer,
89 &QTimer::timeout, q_ptr, [this]() {
90 this->_q_extendedDeviceDiscoveryTimeout();
91 });
92 extendedDiscoveryTimer.setInterval(10000);
93 extendedDiscoveryTimer.setSingleShot(true);
94 }
95 inquiryType = QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry;
96}
97
98QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
99{
100 delete adapter;
101 delete adapterBluez5;
102}
103
104//TODO: Qt6 remove the pendingCancel/pendingStart logic as it is cumbersome.
105// It is a behavior change across all platforms and was initially done
106// for Bluez. The behavior should be similar to QBluetoothServiceDiscoveryAgent
107// PendingCancel creates issues whereby the agent is still shutting down
108// but isActive() below already returns false. This means the isActive() is
109// out of sync with the finished() and cancel() signal.
110
111bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
112{
113 if (pendingStart)
114 return true;
115 if (pendingCancel)
116 return false; //TODO Qt6: remove pending[Cancel|Start] logic (see comment above)
117
118 return (adapter || adapterBluez5);
119}
120
121QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
122{
123 return (ClassicMethod | LowEnergyMethod);
124}
125
126void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
127{
128 // Currently both BlueZ backends do not distinguish discovery methods.
129 // The DBus API's always return both device types. Therefore we ignore
130 // the passed in methods.
131
132 if (pendingCancel == true) {
133 pendingStart = true;
134 return;
135 }
136
137 discoveredDevices.clear();
138
139 if (managerBluez5) {
140 startBluez5(methods);
141 return;
142 }
143
144 QDBusPendingReply<QDBusObjectPath> reply;
145
146 if (m_adapterAddress.isNull())
147 reply = manager->DefaultAdapter();
148 else
149 reply = manager->FindAdapter(m_adapterAddress.toString());
150 reply.waitForFinished();
151
152 if (reply.isError()) {
153 errorString = reply.error().message();
154 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
155 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
156 Q_Q(QBluetoothDeviceDiscoveryAgent);
157 emit q->error(lastError);
158 return;
159 }
160
161 adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"), reply.value().path(),
162 QDBusConnection::systemBus());
163
164 Q_Q(QBluetoothDeviceDiscoveryAgent);
165 QObject::connect(adapter, &OrgBluezAdapterInterface::DeviceFound,
166 q, [this](const QString &address, const QVariantMap &dict) {
167 this->_q_deviceFound(address, dict);
168 });
169 QObject::connect(adapter, &OrgBluezAdapterInterface::PropertyChanged,
170 q, [this](const QString &name, const QDBusVariant &value) {
171 this->_q_propertyChanged(name, value);
172 });
173
174 QDBusPendingReply<QVariantMap> propertiesReply = adapter->GetProperties();
175 propertiesReply.waitForFinished();
176 if (propertiesReply.isError()) {
177 errorString = propertiesReply.error().message();
178 delete adapter;
179 adapter = nullptr;
180 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
181 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
182 Q_Q(QBluetoothDeviceDiscoveryAgent);
183 delete adapter;
184 adapter = nullptr;
185 emit q->error(lastError);
186 return;
187 }
188
189 if (!propertiesReply.value().value(QStringLiteral("Powered")).toBool()) {
190 qCDebug(QT_BT_BLUEZ) << "Aborting device discovery due to offline Bluetooth Adapter";
191 lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
192 errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
193 delete adapter;
194 adapter = nullptr;
195 emit q->error(lastError);
196 return;
197 }
198
199 if (propertiesReply.value().value(QStringLiteral("Discovering")).toBool()) {
200 /* The discovery session is already ongoing. BTLE devices are advertised
201 immediately after the start of the device discovery session. Hence if the
202 session is already ongoing, we have just missed the BTLE device
203 advertisement.
204
205 This always happens during the second device discovery run in
206 the current process. The first discovery doesn't have this issue.
207 As to why the discovery session remains active despite the previous one
208 being terminated is not known. This may be a bug in Bluez4.
209
210 To workaround this issue we have to wait for two discovery
211 sessions cycles.
212 */
213 qCDebug(QT_BT_BLUEZ) << "Using BTLE device discovery workaround.";
214 useExtendedDiscovery = true;
215 } else {
216 useExtendedDiscovery = false;
217 }
218
219 QDBusPendingReply<> discoveryReply = adapter->StartDiscovery();
220 discoveryReply.waitForFinished();
221 if (discoveryReply.isError()) {
222 delete adapter;
223 adapter = nullptr;
224 errorString = discoveryReply.error().message();
225 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
226 Q_Q(QBluetoothDeviceDiscoveryAgent);
227 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
228 emit q->error(lastError);
229 return;
230 }
231}
232
233void QBluetoothDeviceDiscoveryAgentPrivate::startBluez5(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
234{
235 Q_Q(QBluetoothDeviceDiscoveryAgent);
236
237 bool ok = false;
238 const QString adapterPath = findAdapterForAddress(m_adapterAddress, &ok);
239 if (!ok || adapterPath.isEmpty()) {
240 qCWarning(QT_BT_BLUEZ) << "Cannot find Bluez 5 adapter for device search" << ok;
241 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
242 errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
243 q->error(lastError);
244 return;
245 }
246
247 adapterBluez5 = new OrgBluezAdapter1Interface(QStringLiteral("org.bluez"),
248 adapterPath,
249 QDBusConnection::systemBus());
250
251 if (!adapterBluez5->powered()) {
252 qCDebug(QT_BT_BLUEZ) << "Aborting device discovery due to offline Bluetooth Adapter";
253 lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
254 errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
255 delete adapterBluez5;
256 adapterBluez5 = nullptr;
257 emit q->error(lastError);
258 return;
259 }
260
261 QVariantMap map;
262 if (methods == (QBluetoothDeviceDiscoveryAgent::LowEnergyMethod|QBluetoothDeviceDiscoveryAgent::ClassicMethod))
263 map.insert(QStringLiteral("Transport"), QStringLiteral("auto"));
264 else if (methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
265 map.insert(QStringLiteral("Transport"), QStringLiteral("le"));
266 else
267 map.insert(QStringLiteral("Transport"), QStringLiteral("bredr"));
268
269 // older BlueZ 5.x versions don't have this function
270 // filterReply returns UnknownMethod which we ignore
271 QDBusPendingReply<> filterReply = adapterBluez5->SetDiscoveryFilter(map);
272 filterReply.waitForFinished();
273 if (filterReply.isError()) {
274 if (filterReply.error().type() == QDBusError::Other
275 && filterReply.error().name() == QStringLiteral("org.bluez.Error.Failed")) {
276 qCDebug(QT_BT_BLUEZ) << "Discovery method" << methods << "not supported";
277 lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod;
278 errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
279 "are not supported on this platform");
280 delete adapterBluez5;
281 adapterBluez5 = nullptr;
282 emit q->error(lastError);
283 return;
284 } else if (filterReply.error().type() != QDBusError::UnknownMethod) {
285 qCDebug(QT_BT_BLUEZ) << "SetDiscoveryFilter failed:" << filterReply.error();
286 }
287 }
288
289 QtBluezDiscoveryManager::instance()->registerDiscoveryInterest(adapterBluez5->path());
290 QObject::connect(QtBluezDiscoveryManager::instance(), &QtBluezDiscoveryManager::discoveryInterrupted,
291 q, [this](const QString &path){
292 this->_q_discoveryInterrupted(path);
293 });
294
295 // collect initial set of information
296 QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
297 reply.waitForFinished();
298 if (!reply.isError()) {
299 ManagedObjectList managedObjectList = reply.value();
300 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
301 const QDBusObjectPath &path = it.key();
302 const InterfaceList &ifaceList = it.value();
303
304 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
305 const QString &iface = jt.key();
306
307 if (iface == QStringLiteral("org.bluez.Device1")) {
308
309 if (path.path().indexOf(adapterBluez5->path()) != 0)
310 continue; //devices whose path doesn't start with same path we skip
311
312 deviceFoundBluez5(path.path());
313 if (!isActive()) // Can happen if stop() was called from a slot in user code.
314 return;
315 }
316 }
317 }
318 }
319
320 // wait interval and sum up what was found
321 if (!discoveryTimer) {
322 discoveryTimer = new QTimer(q);
323 discoveryTimer->setSingleShot(true);
324 QObject::connect(discoveryTimer, &QTimer::timeout,
325 q, [this]() {
326 this->_q_discoveryFinished();
327 });
328 }
329
330 if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
331 discoveryTimer->setInterval(lowEnergySearchTimeout);
332 discoveryTimer->start();
333 }
334}
335
336void QBluetoothDeviceDiscoveryAgentPrivate::stop()
337{
338 if (!adapter && !adapterBluez5)
339 return;
340
341 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
342 pendingCancel = true;
343 pendingStart = false;
344 if (adapter) {
345 QDBusPendingReply<> reply = adapter->StopDiscovery();
346 reply.waitForFinished();
347 } else {
348 _q_discoveryFinished();
349 }
350}
351
352void QBluetoothDeviceDiscoveryAgentPrivate::_q_deviceFound(const QString &address,
353 const QVariantMap &dict)
354{
355 const QBluetoothAddress btAddress(address);
356 const QString btName = dict.value(QStringLiteral("Name")).toString();
357 quint32 btClass = dict.value(QStringLiteral("Class")).toUInt();
358
359 qCDebug(QT_BT_BLUEZ) << "Discovered: " << address << btName
360 << "Num UUIDs" << dict.value(QStringLiteral("UUIDs")).toStringList().count()
361 << "total device" << discoveredDevices.count() << "cached"
362 << dict.value(QStringLiteral("Cached")).toBool()
363 << "RSSI" << dict.value(QStringLiteral("RSSI")).toInt();
364
365 QBluetoothDeviceInfo device(btAddress, btName, btClass);
366 if (dict.value(QStringLiteral("RSSI")).isValid())
367 device.setRssi(dict.value(QStringLiteral("RSSI")).toInt());
368 QVector<QBluetoothUuid> uuids;
369 const QStringList uuidStrings
370 = dict.value(QLatin1String("UUIDs")).toStringList();
371 for (const QString &u : uuidStrings)
372 uuids.append(QBluetoothUuid(u));
373 device.setServiceUuids(uuids);
374 device.setCached(dict.value(QStringLiteral("Cached")).toBool());
375
376
377 /*
378 * Bluez v4.1 does not have extra bit which gives information if device is Bluetooth
379 * Low Energy device and the way to discover it is with Class property of the Bluetooth device.
380 * Low Energy devices do not have property Class.
381 */
382 if (btClass == 0)
383 device.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
384 else
385 device.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
386 for (int i = 0; i < discoveredDevices.size(); i++) {
387 if (discoveredDevices[i].address() == device.address()) {
388 if (discoveredDevices[i] == device) {
389 qCDebug(QT_BT_BLUEZ) << "Duplicate: " << address;
390 return;
391 }
392 discoveredDevices.replace(i, device);
393 Q_Q(QBluetoothDeviceDiscoveryAgent);
394 qCDebug(QT_BT_BLUEZ) << "Updated: " << address;
395
396 emit q->deviceDiscovered(device);
397 return; // this works if the list doesn't contain duplicates. Don't let it.
398 }
399 }
400 qCDebug(QT_BT_BLUEZ) << "Emit: " << address;
401 discoveredDevices.append(device);
402 Q_Q(QBluetoothDeviceDiscoveryAgent);
403 emit q->deviceDiscovered(device);
404}
405
406void QBluetoothDeviceDiscoveryAgentPrivate::deviceFoundBluez5(const QString& devicePath)
407{
408 Q_Q(QBluetoothDeviceDiscoveryAgent);
409
410 if (!q->isActive())
411 return;
412
413 OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), devicePath,
414 QDBusConnection::systemBus());
415
416 if (device.adapter().path() != adapterBluez5->path())
417 return;
418
419 const QBluetoothAddress btAddress(device.address());
420 if (btAddress.isNull()) // no point reporting an empty address
421 return;
422
423 const QString btName = device.alias();
424 quint32 btClass = device.classProperty();
425
426 qCDebug(QT_BT_BLUEZ) << "Discovered: " << btAddress.toString() << btName
427 << "Num UUIDs" << device.uUIDs().count()
428 << "total device" << discoveredDevices.count() << "cached"
429 << "RSSI" << device.rSSI() << "Class" << btClass
430 << "Num ManufacturerData" << device.manufacturerData().size();
431
432 OrgFreedesktopDBusPropertiesInterface *prop = new OrgFreedesktopDBusPropertiesInterface(
433 QStringLiteral("org.bluez"), devicePath, QDBusConnection::systemBus(), q);
434 QObject::connect(prop, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
435 q, [this](const QString &interface, const QVariantMap &changedProperties,
436 const QStringList &invalidatedProperties) {
437 this->_q_PropertiesChanged(interface, changedProperties, invalidatedProperties);
438 });
439
440 // remember what we have to cleanup
441 propertyMonitors.append(prop);
442
443 // read information
444 QBluetoothDeviceInfo deviceInfo(btAddress, btName, btClass);
445 deviceInfo.setRssi(device.rSSI());
446
447 QVector<QBluetoothUuid> uuids;
448 bool foundLikelyLowEnergyUuid = false;
449 for (const auto &u: device.uUIDs()) {
450 const QBluetoothUuid id(u);
451 if (id.isNull())
452 continue;
453
454 if (!foundLikelyLowEnergyUuid) {
455 //once we found one BTLE service we are done
456 bool ok = false;
457 quint16 shortId = id.toUInt16(&ok);
458 if (ok && ((shortId & QBluetoothUuid::GenericAccess) == QBluetoothUuid::GenericAccess))
459 foundLikelyLowEnergyUuid = true;
460 }
461 uuids.append(id);
462 }
463 deviceInfo.setServiceUuids(uuids);
464
465 if (!btClass) {
466 deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
467 } else {
468 deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
469 if (foundLikelyLowEnergyUuid)
470 deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
471 }
472
473 const ManufacturerDataList deviceManufacturerData = device.manufacturerData();
474 const QList<quint16> keys = deviceManufacturerData.keys();
475 for (quint16 key : keys)
476 deviceInfo.setManufacturerData(
477 key, deviceManufacturerData.value(key).variant().toByteArray());
478
479 for (int i = 0; i < discoveredDevices.size(); i++) {
480 if (discoveredDevices[i].address() == deviceInfo.address()) {
481 if (discoveredDevices[i] == deviceInfo && lowEnergySearchTimeout > 0) {
482 qCDebug(QT_BT_BLUEZ) << "Duplicate: " << btAddress.toString();
483 return;
484 }
485 discoveredDevices.replace(i, deviceInfo);
486
487 emit q->deviceDiscovered(deviceInfo);
488 return; // this works if the list doesn't contain duplicates. Don't let it.
489 }
490 }
491
492 discoveredDevices.append(deviceInfo);
493 emit q->deviceDiscovered(deviceInfo);
494}
495
496void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &name,
497 const QDBusVariant &value)
498{
499 qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << name << value.variant();
500
501 if (name == QLatin1String("Discovering")) {
502 if (!value.variant().toBool()) {
503 Q_Q(QBluetoothDeviceDiscoveryAgent);
504 if (pendingCancel && !pendingStart) {
505 adapter->deleteLater();
506 adapter = nullptr;
507
508 pendingCancel = false;
509 emit q->canceled();
510 } else if (pendingStart) {
511 adapter->deleteLater();
512 adapter = nullptr;
513
514 pendingStart = false;
515 pendingCancel = false;
516 // start parameter ignored since Bluez 4 doesn't distinguish them
517 start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
518 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
519 } else {
520 // happens when agent is created while other agent called StopDiscovery()
521 if (!adapter)
522 return;
523
524 if (useExtendedDiscovery) {
525 useExtendedDiscovery = false;
526 /* We don't use the Start/StopDiscovery combo here
527 Using this combo surppresses the BTLE device.
528 */
529 extendedDiscoveryTimer.start();
530 return;
531 }
532
533 QDBusPendingReply<> reply = adapter->StopDiscovery();
534 reply.waitForFinished();
535 adapter->deleteLater();
536 adapter = nullptr;
537 emit q->finished();
538 }
539 } else {
540 if (extendedDiscoveryTimer.isActive())
541 extendedDiscoveryTimer.stop();
542 }
543 }
544}
545
546void QBluetoothDeviceDiscoveryAgentPrivate::_q_extendedDeviceDiscoveryTimeout()
547{
548
549 if (adapter) {
550 adapter->deleteLater();
551 adapter = nullptr;
552 }
553 if (isActive()) {
554 Q_Q(QBluetoothDeviceDiscoveryAgent);
555 emit q->finished();
556 }
557}
558
559void QBluetoothDeviceDiscoveryAgentPrivate::_q_InterfacesAdded(const QDBusObjectPath &object_path,
560 InterfaceList interfaces_and_properties)
561{
562 Q_Q(QBluetoothDeviceDiscoveryAgent);
563
564 if (!q->isActive())
565 return;
566
567 if (interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))) {
568 // device interfaces belonging to different adapter
569 // will be filtered out by deviceFoundBluez5();
570 deviceFoundBluez5(object_path.path());
571 }
572}
573
574void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished()
575{
576 Q_Q(QBluetoothDeviceDiscoveryAgent);
577
578 if (discoveryTimer)
579 discoveryTimer->stop();
580
581 QtBluezDiscoveryManager::instance()->disconnect(q);
582 QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapterBluez5->path());
583
584 qDeleteAll(propertyMonitors);
585 propertyMonitors.clear();
586
587 delete adapterBluez5;
588 adapterBluez5 = nullptr;
589
590 if (pendingCancel && !pendingStart) {
591 pendingCancel = false;
592 emit q->canceled();
593 } else if (pendingStart) {
594 pendingStart = false;
595 pendingCancel = false;
596 start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
597 | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
598 } else {
599 emit q->finished();
600 }
601}
602
603void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryInterrupted(const QString &path)
604{
605 Q_Q(QBluetoothDeviceDiscoveryAgent);
606
607 if (!q->isActive())
608 return;
609
610 if (path == adapterBluez5->path()) {
611 qCWarning(QT_BT_BLUEZ) << "Device discovery aborted due to unexpected adapter changes from another process.";
612
613 if (discoveryTimer)
614 discoveryTimer->stop();
615
616 QtBluezDiscoveryManager::instance()->disconnect(q);
617 // no need to call unregisterDiscoveryInterest since QtBluezDiscoveryManager
618 // does this automatically when emitting discoveryInterrupted(QString) signal
619
620 delete adapterBluez5;
621 adapterBluez5 = nullptr;
622
623 errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter error");
624 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
625 emit q->error(lastError);
626 }
627}
628
629void QBluetoothDeviceDiscoveryAgentPrivate::_q_PropertiesChanged(const QString &interface,
630 const QVariantMap &changed_properties,
631 const QStringList &)
632{
633 Q_Q(QBluetoothDeviceDiscoveryAgent);
634 if (interface == QStringLiteral("org.bluez.Device1")
635 && (changed_properties.contains(QStringLiteral("RSSI"))
636 || changed_properties.contains(QStringLiteral("ManufacturerData")))) {
637 OrgFreedesktopDBusPropertiesInterface *props =
638 qobject_cast<OrgFreedesktopDBusPropertiesInterface *>(q->sender());
639 if (!props)
640 return;
641
642 OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), props->path(),
643 QDBusConnection::systemBus());
644 for (int i = 0; i < discoveredDevices.size(); i++) {
645 if (discoveredDevices[i].address().toString() == device.address()) {
646 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
647 if (changed_properties.contains(QStringLiteral("RSSI"))) {
648 qCDebug(QT_BT_BLUEZ) << "Updating RSSI for" << device.address()
649 << changed_properties.value(QStringLiteral("RSSI"));
650 discoveredDevices[i].setRssi(
651 changed_properties.value(QStringLiteral("RSSI")).toInt());
652 updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
653 }
654 if (changed_properties.contains(QStringLiteral("ManufacturerData"))) {
655 qCDebug(QT_BT_BLUEZ) << "Updating ManufacturerData for" << device.address();
656 ManufacturerDataList changedManufacturerData =
657 qdbus_cast< ManufacturerDataList >(changed_properties.value(QStringLiteral("ManufacturerData")));
658
659 const QList<quint16> keys = changedManufacturerData.keys();
660 for (quint16 key : keys) {
661 if (discoveredDevices[i].setManufacturerData(key, changedManufacturerData.value(key).variant().toByteArray()))
662 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
663 }
664 }
665 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
666 emit q->deviceUpdated(discoveredDevices[i], updatedFields);
667 return;
668 }
669 }
670 }
671}
672
673QT_END_NAMESPACE
674