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 
56 QT_BEGIN_NAMESPACE
57 
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)58 Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
59 
60 QBluetoothDeviceDiscoveryAgentPrivate::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 
~QBluetoothDeviceDiscoveryAgentPrivate()98 QBluetoothDeviceDiscoveryAgentPrivate::~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 
isActive() const111 bool 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 
supportedDiscoveryMethods()121 QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
122 {
123     return (ClassicMethod | LowEnergyMethod);
124 }
125 
start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)126 void 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     devicesProperties.clear();
139 
140     if (managerBluez5) {
141         startBluez5(methods);
142         return;
143     }
144 
145     QDBusPendingReply<QDBusObjectPath> reply;
146 
147     if (m_adapterAddress.isNull())
148         reply = manager->DefaultAdapter();
149     else
150         reply = manager->FindAdapter(m_adapterAddress.toString());
151     reply.waitForFinished();
152 
153     if (reply.isError()) {
154         errorString = reply.error().message();
155         qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
156         lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
157         Q_Q(QBluetoothDeviceDiscoveryAgent);
158         emit q->error(lastError);
159         return;
160     }
161 
162     adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"), reply.value().path(),
163                                            QDBusConnection::systemBus());
164 
165     Q_Q(QBluetoothDeviceDiscoveryAgent);
166     QObject::connect(adapter, &OrgBluezAdapterInterface::DeviceFound,
167                      q, [this](const QString &address, const QVariantMap &dict) {
168         this->_q_deviceFound(address, dict);
169     });
170     QObject::connect(adapter, &OrgBluezAdapterInterface::PropertyChanged,
171                      q, [this](const QString &name, const QDBusVariant &value) {
172         this->_q_propertyChanged(name, value);
173     });
174 
175     QDBusPendingReply<QVariantMap> propertiesReply = adapter->GetProperties();
176     propertiesReply.waitForFinished();
177     if (propertiesReply.isError()) {
178         errorString = propertiesReply.error().message();
179         delete adapter;
180         adapter = nullptr;
181         qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
182         lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
183         Q_Q(QBluetoothDeviceDiscoveryAgent);
184         delete adapter;
185         adapter = nullptr;
186         emit q->error(lastError);
187         return;
188     }
189 
190     if (!propertiesReply.value().value(QStringLiteral("Powered")).toBool()) {
191         qCDebug(QT_BT_BLUEZ) << "Aborting device discovery due to offline Bluetooth Adapter";
192         lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
193         errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
194         delete adapter;
195         adapter = nullptr;
196         emit q->error(lastError);
197         return;
198     }
199 
200     if (propertiesReply.value().value(QStringLiteral("Discovering")).toBool()) {
201         /*  The discovery session is already ongoing. BTLE devices are advertised
202             immediately after the start of the device discovery session. Hence if the
203             session is already ongoing, we have just missed the BTLE device
204             advertisement.
205 
206             This always happens during the second device discovery run in
207             the current process. The first discovery doesn't have this issue.
208             As to why the discovery session remains active despite the previous one
209             being terminated is not known. This may be a bug in Bluez4.
210 
211             To workaround this issue we have to wait for two discovery
212             sessions cycles.
213         */
214         qCDebug(QT_BT_BLUEZ) << "Using BTLE device discovery workaround.";
215         useExtendedDiscovery = true;
216     } else {
217         useExtendedDiscovery = false;
218     }
219 
220     QDBusPendingReply<> discoveryReply = adapter->StartDiscovery();
221     discoveryReply.waitForFinished();
222     if (discoveryReply.isError()) {
223         delete adapter;
224         adapter = nullptr;
225         errorString = discoveryReply.error().message();
226         lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
227         Q_Q(QBluetoothDeviceDiscoveryAgent);
228         qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "ERROR: " << errorString;
229         emit q->error(lastError);
230         return;
231     }
232 }
233 
startBluez5(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)234 void QBluetoothDeviceDiscoveryAgentPrivate::startBluez5(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
235 {
236     Q_Q(QBluetoothDeviceDiscoveryAgent);
237 
238     bool ok = false;
239     const QString adapterPath = findAdapterForAddress(m_adapterAddress, &ok);
240     if (!ok || adapterPath.isEmpty()) {
241         qCWarning(QT_BT_BLUEZ) << "Cannot find Bluez 5 adapter for device search" << ok;
242         lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
243         errorString = QBluetoothDeviceDiscoveryAgent::tr("Cannot find valid Bluetooth adapter.");
244         q->error(lastError);
245         return;
246     }
247 
248     adapterBluez5 = new OrgBluezAdapter1Interface(QStringLiteral("org.bluez"),
249                                                   adapterPath,
250                                                   QDBusConnection::systemBus());
251 
252     if (!adapterBluez5->powered()) {
253         qCDebug(QT_BT_BLUEZ) << "Aborting device discovery due to offline Bluetooth Adapter";
254         lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
255         errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
256         delete adapterBluez5;
257         adapterBluez5 = nullptr;
258         emit q->error(lastError);
259         return;
260     }
261 
262     QVariantMap map;
263     if (methods == (QBluetoothDeviceDiscoveryAgent::LowEnergyMethod|QBluetoothDeviceDiscoveryAgent::ClassicMethod))
264         map.insert(QStringLiteral("Transport"), QStringLiteral("auto"));
265     else if (methods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)
266         map.insert(QStringLiteral("Transport"), QStringLiteral("le"));
267     else
268         map.insert(QStringLiteral("Transport"), QStringLiteral("bredr"));
269 
270     // older BlueZ 5.x versions don't have this function
271     // filterReply returns UnknownMethod which we ignore
272     QDBusPendingReply<> filterReply = adapterBluez5->SetDiscoveryFilter(map);
273     filterReply.waitForFinished();
274     if (filterReply.isError()) {
275         if (filterReply.error().type() == QDBusError::Other
276                     && filterReply.error().name() == QStringLiteral("org.bluez.Error.Failed")) {
277             qCDebug(QT_BT_BLUEZ) << "Discovery method" << methods << "not supported";
278             lastError = QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod;
279             errorString = QBluetoothDeviceDiscoveryAgent::tr("One or more device discovery methods "
280                                                              "are not supported on this platform");
281             delete adapterBluez5;
282             adapterBluez5 = nullptr;
283             emit q->error(lastError);
284             return;
285         } else if (filterReply.error().type() != QDBusError::UnknownMethod) {
286             qCDebug(QT_BT_BLUEZ) << "SetDiscoveryFilter failed:" << filterReply.error();
287         }
288     }
289 
290     QtBluezDiscoveryManager::instance()->registerDiscoveryInterest(adapterBluez5->path());
291     QObject::connect(QtBluezDiscoveryManager::instance(), &QtBluezDiscoveryManager::discoveryInterrupted,
292                      q, [this](const QString &path){
293         this->_q_discoveryInterrupted(path);
294     });
295     OrgFreedesktopDBusPropertiesInterface *prop = new OrgFreedesktopDBusPropertiesInterface(
296                 QStringLiteral("org.bluez"), QStringLiteral(""), QDBusConnection::systemBus());
297     QObject::connect(prop, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
298                      q, [this](const QString &interface, const QVariantMap &changedProperties,
299                      const QStringList &invalidatedProperties,
300                      const QDBusMessage &signal) {
301         this->_q_PropertiesChanged(interface, signal.path(), changedProperties, invalidatedProperties);
302     });
303 
304     // remember what we have to cleanup
305     propertyMonitors.append(prop);
306 
307     // collect initial set of information
308     QDBusPendingReply<ManagedObjectList> reply = managerBluez5->GetManagedObjects();
309     reply.waitForFinished();
310     if (!reply.isError()) {
311         ManagedObjectList managedObjectList = reply.value();
312         for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
313             const QDBusObjectPath &path = it.key();
314             const InterfaceList &ifaceList = it.value();
315 
316             for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
317                 const QString &iface = jt.key();
318 
319                 if (iface == QStringLiteral("org.bluez.Device1")) {
320 
321                     if (path.path().indexOf(adapterBluez5->path()) != 0)
322                         continue; //devices whose path doesn't start with same path we skip
323 
324                     deviceFoundBluez5(path.path(), jt.value());
325                     if (!isActive()) // Can happen if stop() was called from a slot in user code.
326                       return;
327                 }
328             }
329         }
330     }
331 
332     // wait interval and sum up what was found
333     if (!discoveryTimer) {
334         discoveryTimer = new QTimer(q);
335         discoveryTimer->setSingleShot(true);
336         QObject::connect(discoveryTimer, &QTimer::timeout,
337                          q, [this]() {
338             this->_q_discoveryFinished();
339         });
340     }
341 
342     if (lowEnergySearchTimeout > 0) { // otherwise no timeout and stop() required
343         discoveryTimer->setInterval(lowEnergySearchTimeout);
344         discoveryTimer->start();
345     }
346 }
347 
stop()348 void QBluetoothDeviceDiscoveryAgentPrivate::stop()
349 {
350     if (!adapter && !adapterBluez5)
351         return;
352 
353     qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO;
354     pendingCancel = true;
355     pendingStart = false;
356     if (adapter) {
357         QDBusPendingReply<> reply = adapter->StopDiscovery();
358         reply.waitForFinished();
359     } else {
360         _q_discoveryFinished();
361     }
362 }
363 
_q_deviceFound(const QString & address,const QVariantMap & dict)364 void QBluetoothDeviceDiscoveryAgentPrivate::_q_deviceFound(const QString &address,
365                                                            const QVariantMap &dict)
366 {
367     const QBluetoothAddress btAddress(address);
368     const QString btName = dict.value(QStringLiteral("Name")).toString();
369     quint32 btClass = dict.value(QStringLiteral("Class")).toUInt();
370 
371     qCDebug(QT_BT_BLUEZ) << "Discovered: " << address << btName
372                          << "Num UUIDs" << dict.value(QStringLiteral("UUIDs")).toStringList().count()
373                          << "total device" << discoveredDevices.count() << "cached"
374                          << dict.value(QStringLiteral("Cached")).toBool()
375                          << "RSSI" << dict.value(QStringLiteral("RSSI")).toInt();
376 
377     QBluetoothDeviceInfo device(btAddress, btName, btClass);
378     if (dict.value(QStringLiteral("RSSI")).isValid())
379         device.setRssi(dict.value(QStringLiteral("RSSI")).toInt());
380     QVector<QBluetoothUuid> uuids;
381     const QStringList uuidStrings
382             = dict.value(QLatin1String("UUIDs")).toStringList();
383     for (const QString &u : uuidStrings)
384         uuids.append(QBluetoothUuid(u));
385     device.setServiceUuids(uuids);
386     device.setCached(dict.value(QStringLiteral("Cached")).toBool());
387 
388 
389     /*
390      * Bluez v4.1 does not have extra bit which gives information if device is Bluetooth
391      * Low Energy device and the way to discover it is with Class property of the Bluetooth device.
392      * Low Energy devices do not have property Class.
393      */
394     if (btClass == 0)
395         device.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
396     else
397         device.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
398     for (int i = 0; i < discoveredDevices.size(); i++) {
399         if (discoveredDevices[i].address() == device.address()) {
400             if (discoveredDevices[i] == device) {
401                 qCDebug(QT_BT_BLUEZ) << "Duplicate: " << address;
402                 return;
403             }
404             discoveredDevices.replace(i, device);
405             Q_Q(QBluetoothDeviceDiscoveryAgent);
406             qCDebug(QT_BT_BLUEZ) << "Updated: " << address;
407 
408             emit q->deviceDiscovered(device);
409             return; // this works if the list doesn't contain duplicates. Don't let it.
410         }
411     }
412     qCDebug(QT_BT_BLUEZ) << "Emit: " << address;
413     discoveredDevices.append(device);
414     Q_Q(QBluetoothDeviceDiscoveryAgent);
415     emit q->deviceDiscovered(device);
416 }
417 
418 // Returns invalid QBluetoothDeviceInfo in case of error
createDeviceInfoFromBluez5Device(const QVariantMap & properties)419 static QBluetoothDeviceInfo createDeviceInfoFromBluez5Device(const QVariantMap& properties)
420 {
421     const QBluetoothAddress btAddress(properties[QStringLiteral("Address")].toString());
422     if (btAddress.isNull())
423         return QBluetoothDeviceInfo();
424 
425     const QString btName = properties[QStringLiteral("Alias")].toString();
426     quint32 btClass = properties[QStringLiteral("Class")].toUInt();
427 
428     QBluetoothDeviceInfo deviceInfo(btAddress, btName, btClass);
429     deviceInfo.setRssi(qvariant_cast<short>(properties[QStringLiteral("RSSI")]));
430 
431     QVector<QBluetoothUuid> uuids;
432     bool foundLikelyLowEnergyUuid = false;
433     const QStringList foundUuids = qvariant_cast<QStringList>(properties[QStringLiteral("UUIDs")]);
434     for (const auto &u: foundUuids) {
435         const QBluetoothUuid id(u);
436         if (id.isNull())
437             continue;
438 
439         if (!foundLikelyLowEnergyUuid) {
440             //once we found one BTLE service we are done
441             bool ok = false;
442             quint16 shortId = id.toUInt16(&ok);
443             if (ok && ((shortId & QBluetoothUuid::GenericAccess) == QBluetoothUuid::GenericAccess))
444                 foundLikelyLowEnergyUuid = true;
445         }
446         uuids.append(id);
447     }
448     deviceInfo.setServiceUuids(uuids);
449 
450     if (!btClass) {
451         deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
452     } else {
453         deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
454         if (foundLikelyLowEnergyUuid)
455             deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
456     }
457 
458     const ManufacturerDataList deviceManufacturerData = qdbus_cast<ManufacturerDataList>(properties[QStringLiteral("ManufacturerData")]);
459     const QList<quint16> keys = deviceManufacturerData.keys();
460     for (quint16 key : keys)
461         deviceInfo.setManufacturerData(
462                     key, deviceManufacturerData.value(key).variant().toByteArray());
463 
464     return deviceInfo;
465 }
466 
deviceFoundBluez5(const QString & devicePath,const QVariantMap & properties)467 void QBluetoothDeviceDiscoveryAgentPrivate::deviceFoundBluez5(const QString &devicePath,
468                                                               const QVariantMap &properties)
469 {
470     Q_Q(QBluetoothDeviceDiscoveryAgent);
471 
472     if (!q->isActive())
473         return;
474 
475     auto deviceAdapter = qvariant_cast<QDBusObjectPath>(properties[QStringLiteral("Adapter")]);
476      if (deviceAdapter.path() != adapterBluez5->path())
477          return;
478 
479     // read information
480     QBluetoothDeviceInfo deviceInfo = createDeviceInfoFromBluez5Device(properties);
481     if (!deviceInfo.isValid()) // no point reporting an empty address
482         return;
483 
484     qCDebug(QT_BT_BLUEZ) << "Discovered: " << deviceInfo.name() << deviceInfo.address()
485                          << "Num UUIDs" << deviceInfo.serviceUuids().count()
486                          << "total device" << discoveredDevices.count() << "cached"
487                          << "RSSI" << deviceInfo.rssi()
488                          << "Num ManufacturerData" << deviceInfo.manufacturerData().size();
489 
490     // Cache the properties so we do not have to access dbus every time to get a value
491     devicesProperties[devicePath] = properties;
492 
493     for (int i = 0; i < discoveredDevices.size(); i++) {
494         if (discoveredDevices[i].address() == deviceInfo.address()) {
495             if (lowEnergySearchTimeout > 0 && discoveredDevices[i] == deviceInfo) {
496                 qCDebug(QT_BT_BLUEZ) << "Duplicate: " << deviceInfo.address();
497                 return;
498             }
499             discoveredDevices.replace(i, deviceInfo);
500 
501             emit q->deviceDiscovered(deviceInfo);
502             return; // this works if the list doesn't contain duplicates. Don't let it.
503         }
504     }
505 
506     discoveredDevices.append(deviceInfo);
507     emit q->deviceDiscovered(deviceInfo);
508 }
509 
_q_propertyChanged(const QString & name,const QDBusVariant & value)510 void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &name,
511                                                                const QDBusVariant &value)
512 {
513     qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << name << value.variant();
514 
515     if (name == QLatin1String("Discovering")) {
516       if (!value.variant().toBool()) {
517             Q_Q(QBluetoothDeviceDiscoveryAgent);
518             if (pendingCancel && !pendingStart) {
519                 adapter->deleteLater();
520                 adapter = nullptr;
521 
522                 pendingCancel = false;
523                 emit q->canceled();
524             } else if (pendingStart) {
525                 adapter->deleteLater();
526                 adapter = nullptr;
527 
528                 pendingStart = false;
529                 pendingCancel = false;
530                 // start parameter ignored since Bluez 4 doesn't distinguish them
531                 start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
532                       | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
533             } else {
534                  // happens when agent is created while other agent called StopDiscovery()
535                 if (!adapter)
536                     return;
537 
538                 if (useExtendedDiscovery) {
539                     useExtendedDiscovery = false;
540                     /* We don't use the Start/StopDiscovery combo here
541                        Using this combo surppresses the BTLE device.
542                     */
543                     extendedDiscoveryTimer.start();
544                     return;
545                 }
546 
547                 QDBusPendingReply<> reply = adapter->StopDiscovery();
548                 reply.waitForFinished();
549                 adapter->deleteLater();
550                 adapter = nullptr;
551                 emit q->finished();
552             }
553         } else {
554             if (extendedDiscoveryTimer.isActive())
555                 extendedDiscoveryTimer.stop();
556         }
557     }
558 }
559 
_q_extendedDeviceDiscoveryTimeout()560 void QBluetoothDeviceDiscoveryAgentPrivate::_q_extendedDeviceDiscoveryTimeout()
561 {
562 
563     if (adapter) {
564         adapter->deleteLater();
565         adapter = nullptr;
566     }
567     if (isActive()) {
568         Q_Q(QBluetoothDeviceDiscoveryAgent);
569         emit q->finished();
570     }
571 }
572 
_q_InterfacesAdded(const QDBusObjectPath & object_path,InterfaceList interfaces_and_properties)573 void QBluetoothDeviceDiscoveryAgentPrivate::_q_InterfacesAdded(const QDBusObjectPath &object_path,
574                                                                InterfaceList interfaces_and_properties)
575 {
576     Q_Q(QBluetoothDeviceDiscoveryAgent);
577 
578     if (!q->isActive())
579         return;
580 
581     if (interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))) {
582         // device interfaces belonging to different adapter
583         // will be filtered out by deviceFoundBluez5();
584         deviceFoundBluez5(object_path.path(), interfaces_and_properties[QStringLiteral("org.bluez.Device1")]);
585     }
586 }
587 
_q_discoveryFinished()588 void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryFinished()
589 {
590     Q_Q(QBluetoothDeviceDiscoveryAgent);
591 
592     if (discoveryTimer)
593         discoveryTimer->stop();
594 
595     QtBluezDiscoveryManager::instance()->disconnect(q);
596     QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapterBluez5->path());
597 
598     qDeleteAll(propertyMonitors);
599     propertyMonitors.clear();
600 
601     delete adapterBluez5;
602     adapterBluez5 = nullptr;
603 
604     if (pendingCancel && !pendingStart) {
605         pendingCancel = false;
606         emit q->canceled();
607     } else if (pendingStart) {
608         pendingStart = false;
609         pendingCancel = false;
610         start(QBluetoothDeviceDiscoveryAgent::ClassicMethod
611               | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
612     } else {
613         emit q->finished();
614     }
615 }
616 
_q_discoveryInterrupted(const QString & path)617 void QBluetoothDeviceDiscoveryAgentPrivate::_q_discoveryInterrupted(const QString &path)
618 {
619     Q_Q(QBluetoothDeviceDiscoveryAgent);
620 
621     if (!q->isActive())
622         return;
623 
624     if (path == adapterBluez5->path()) {
625         qCWarning(QT_BT_BLUEZ) << "Device discovery aborted due to unexpected adapter changes from another process.";
626 
627         if (discoveryTimer)
628             discoveryTimer->stop();
629 
630         QtBluezDiscoveryManager::instance()->disconnect(q);
631         // no need to call unregisterDiscoveryInterest since QtBluezDiscoveryManager
632         // does this automatically when emitting discoveryInterrupted(QString) signal
633 
634         delete adapterBluez5;
635         adapterBluez5 = nullptr;
636 
637         errorString = QBluetoothDeviceDiscoveryAgent::tr("Bluetooth adapter error");
638         lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
639         emit q->error(lastError);
640     }
641 }
642 
_q_PropertiesChanged(const QString & interface,const QString & path,const QVariantMap & changed_properties,const QStringList & invalidated_properties)643 void QBluetoothDeviceDiscoveryAgentPrivate::_q_PropertiesChanged(const QString &interface,
644                                                                  const QString &path,
645                                                                  const QVariantMap &changed_properties,
646                                                                  const QStringList &invalidated_properties)
647 {
648     Q_Q(QBluetoothDeviceDiscoveryAgent);
649     if (interface != QStringLiteral("org.bluez.Device1"))
650         return;
651 
652     if (!devicesProperties.contains(path))
653         return;
654 
655     // Update the cached properties before checking changed_properties for RSSI and ManufacturerData
656     // so the cached properties are always up to date.
657     QVariantMap & properties = devicesProperties[path];
658     for (QVariantMap::const_iterator it = changed_properties.constBegin();
659          it != changed_properties.constEnd(); ++it) {
660         properties[it.key()] = it.value();
661     }
662 
663     for (const QString & property : invalidated_properties)
664         properties.remove(property);
665 
666     const auto info = createDeviceInfoFromBluez5Device(properties);
667     if (!info.isValid())
668         return;
669 
670     if (changed_properties.contains(QStringLiteral("RSSI"))
671         || changed_properties.contains(QStringLiteral("ManufacturerData"))) {
672 
673         for (int i = 0; i < discoveredDevices.size(); i++) {
674             if (discoveredDevices[i].address() == info.address()) {
675                 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
676                 if (changed_properties.contains(QStringLiteral("RSSI"))) {
677                     qCDebug(QT_BT_BLUEZ) << "Updating RSSI for" << info.address()
678                                          << changed_properties.value(QStringLiteral("RSSI"));
679                     discoveredDevices[i].setRssi(
680                                 changed_properties.value(QStringLiteral("RSSI")).toInt());
681                     updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
682                 }
683                 if (changed_properties.contains(QStringLiteral("ManufacturerData"))) {
684                     qCDebug(QT_BT_BLUEZ) << "Updating ManufacturerData for" << info.address();
685                     ManufacturerDataList changedManufacturerData =
686                             qdbus_cast< ManufacturerDataList >(changed_properties.value(QStringLiteral("ManufacturerData")));
687 
688                     const QList<quint16> keys = changedManufacturerData.keys();
689                     bool wasNewValue = false;
690                     for (quint16 key : keys) {
691                         bool added = discoveredDevices[i].setManufacturerData(key, changedManufacturerData.value(key).variant().toByteArray());
692                         wasNewValue = (wasNewValue || added);
693                     }
694 
695                     if (wasNewValue)
696                         updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
697                 }
698 
699                 if (lowEnergySearchTimeout > 0) {
700                     if (discoveredDevices[i] != info) { // field other than manufacturer or rssi changed
701                         if (discoveredDevices.at(i).name() == info.name()) {
702                             qCDebug(QT_BT_BLUEZ) << "Almost Duplicate " << info.address()
703                                                    << info.name() << "- replacing in place";
704                             discoveredDevices.replace(i, info);
705                             emit q->deviceDiscovered(info);
706                         }
707                     } else {
708                         if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
709                             emit q->deviceUpdated(discoveredDevices[i], updatedFields);
710                     }
711 
712                     return;
713                 }
714 
715                 discoveredDevices.replace(i, info);
716                 emit q_ptr->deviceDiscovered(discoveredDevices[i]);
717 
718                 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
719                     emit q->deviceUpdated(discoveredDevices[i], updatedFields);
720                 return;
721             }
722         }
723     }
724 }
725 QT_END_NAMESPACE
726