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 | |
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 | |
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 | |
111 | 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 | |
121 | QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() |
122 | { |
123 | return (ClassicMethod | LowEnergyMethod); |
124 | } |
125 | |
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 | |
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 | |
233 | void 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 | |
336 | void 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 | |
352 | void 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 | |
406 | void 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 | |
496 | void 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 | |
546 | void 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 | |
559 | void 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 | |
574 | void 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 | |
603 | void 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 | |
629 | void 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 | |
673 | QT_END_NAMESPACE |
674 | |