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