1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qbluetoothhostinfo.h" |
5 | #include "qbluetoothlocaldevice.h" |
6 | #include "qbluetoothservicediscoveryagent.h" |
7 | #include "qbluetoothservicediscoveryagent_p.h" |
8 | |
9 | #include "qbluetoothdevicediscoveryagent.h" |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \class QBluetoothServiceDiscoveryAgent |
15 | \inmodule QtBluetooth |
16 | \brief The QBluetoothServiceDiscoveryAgent class enables you to query for |
17 | Bluetooth services. |
18 | |
19 | \since 5.2 |
20 | |
21 | The discovery process relies on the Bluetooth Service Discovery Process (SDP). |
22 | The following steps are required to query the services provided by all contactable |
23 | Bluetooth devices: |
24 | |
25 | \list |
26 | \li create an instance of QBluetoothServiceDiscoveryAgent, |
27 | \li connect to either the serviceDiscovered() or finished() signals, |
28 | \li and call start(). |
29 | \endlist |
30 | |
31 | \snippet doc_src_qtbluetooth.cpp service_discovery |
32 | |
33 | By default a minimal service discovery is performed. In this mode, the returned \l QBluetoothServiceInfo |
34 | objects are guaranteed to contain only device and service UUID information. Depending |
35 | on platform and device capabilities, other service information may also be available. |
36 | The minimal service discovery mode relies on cached SDP data of the platform. Therefore it |
37 | is possible that this discovery does not find a device although it is physically available. |
38 | In such cases a full discovery must be performed to force an update of the platform cache. |
39 | However for most use cases a minimal discovery is adequate as it is much quicker and other |
40 | classes which require up-to-date information such as QBluetoothSocket::connectToService() |
41 | will perform additional discovery if required. If the full service information is required, |
42 | pass \l FullDiscovery as the discoveryMode parameter to start(). |
43 | |
44 | This class may internally utilize \l QBluetoothDeviceDiscoveryAgent to find unknown devices. |
45 | |
46 | The service discovery may find Bluetooth Low Energy services too if the target device |
47 | is a combination of a classic and Low Energy device. Those devices are required to advertise |
48 | their Low Energy services via SDP. If the target device only supports Bluetooth Low |
49 | Energy services, it is likely to not advertise them via SDP. The \l QLowEnergyController class |
50 | should be utilized to perform the service discovery on Low Energy devices. |
51 | |
52 | On iOS, this class cannot be used because the platform does not expose |
53 | an API which may permit access to QBluetoothServiceDiscoveryAgent related features. |
54 | |
55 | \sa QBluetoothDeviceDiscoveryAgent, QLowEnergyController |
56 | */ |
57 | |
58 | /*! |
59 | \enum QBluetoothServiceDiscoveryAgent::Error |
60 | |
61 | This enum describes errors that can occur during service discovery. |
62 | |
63 | \value NoError No error has occurred. |
64 | \value PoweredOffError The Bluetooth adaptor is powered off, power it on before doing discovery. |
65 | \value InputOutputError Writing or reading from the device resulted in an error. |
66 | \value [since 5.3] InvalidBluetoothAdapterError The passed local adapter address does not |
67 | match the physical adapter address of any |
68 | local Bluetooth device. |
69 | \value [since 6.4] MissingPermissionsError The operating system requests |
70 | permissions which were not |
71 | granted by the user. |
72 | \value UnknownError An unknown error has occurred. |
73 | */ |
74 | |
75 | /*! |
76 | \enum QBluetoothServiceDiscoveryAgent::DiscoveryMode |
77 | |
78 | This enum describes the service discovery mode. |
79 | |
80 | \value MinimalDiscovery Performs a minimal service discovery. The QBluetoothServiceInfo |
81 | objects returned may be incomplete and are only guaranteed to contain device and service UUID information. |
82 | Since a minimal discovery relies on cached SDP data it may not find a physically existing |
83 | device until a \c FullDiscovery is performed. |
84 | \value FullDiscovery Performs a full service discovery. |
85 | */ |
86 | |
87 | /*! |
88 | \fn QBluetoothServiceDiscoveryAgent::serviceDiscovered(const QBluetoothServiceInfo &info) |
89 | |
90 | This signal is emitted when the Bluetooth service described by \a info is discovered. |
91 | |
92 | \note The passed \l QBluetoothServiceInfo parameter may contain a Bluetooth Low Energy |
93 | service if the target device advertises the service via SDP. This is required from device |
94 | which support both, classic Bluetooth (BaseRate) and Low Energy services. |
95 | |
96 | \sa QBluetoothDeviceInfo::coreConfigurations() |
97 | */ |
98 | |
99 | /*! |
100 | \fn QBluetoothServiceDiscoveryAgent::finished() |
101 | |
102 | This signal is emitted when the Bluetooth service discovery completes. |
103 | |
104 | Unlike the \l QBluetoothDeviceDiscoveryAgent::finished() signal this |
105 | signal will even be emitted when an error occurred during the service discovery. Therefore |
106 | it is recommended to check the \l errorOccurred() signal to evaluate the success of the |
107 | service discovery discovery. |
108 | */ |
109 | |
110 | /*! |
111 | \fn void QBluetoothServiceDiscoveryAgent::errorOccurred(QBluetoothServiceDiscoveryAgent::Error |
112 | error) |
113 | |
114 | This signal is emitted when an \a error occurs. The \a error parameter describes the error that |
115 | occurred. |
116 | |
117 | \since 6.2 |
118 | */ |
119 | |
120 | /*! |
121 | Constructs a new QBluetoothServiceDiscoveryAgent with \a parent. The search is performed via the |
122 | local default Bluetooth adapter. |
123 | */ |
124 | QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent) |
125 | : QObject(parent), |
126 | d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, QBluetoothAddress())) |
127 | { |
128 | } |
129 | |
130 | /*! |
131 | Constructs a new QBluetoothServiceDiscoveryAgent for \a deviceAdapter and with \a parent. |
132 | |
133 | It uses \a deviceAdapter for the service search. If \a deviceAdapter is default constructed |
134 | the resulting QBluetoothServiceDiscoveryAgent object will use the local default Bluetooth adapter. |
135 | |
136 | If a \a deviceAdapter is specified that is not a local adapter \l error() will be set to |
137 | \l InvalidBluetoothAdapterError. Therefore it is recommended to test the error flag immediately after |
138 | using this constructor. |
139 | |
140 | \note On WinRT the passed adapter address will be ignored. |
141 | |
142 | \note On Android passing any \a deviceAdapter address is meaningless as Android 6.0 or later does not publish |
143 | the local Bluetooth address anymore. Subsequently, the passed adapter address can never be matched |
144 | against the local adapter address. Therefore the subsequent call to \l start() will always trigger |
145 | \l InvalidBluetoothAdapterError. |
146 | |
147 | \sa error() |
148 | */ |
149 | QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent) |
150 | : QObject(parent), |
151 | d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, deviceAdapter)) |
152 | { |
153 | if (!deviceAdapter.isNull()) { |
154 | const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices(); |
155 | for (const QBluetoothHostInfo &hostInfo : localDevices) { |
156 | if (hostInfo.address() == deviceAdapter) |
157 | return; |
158 | } |
159 | d_ptr->error = InvalidBluetoothAdapterError; |
160 | d_ptr->errorString = tr(s: "Invalid Bluetooth adapter address" ); |
161 | } |
162 | } |
163 | |
164 | /*! |
165 | |
166 | Destructor for QBluetoothServiceDiscoveryAgent |
167 | |
168 | */ |
169 | |
170 | QBluetoothServiceDiscoveryAgent::~QBluetoothServiceDiscoveryAgent() |
171 | { |
172 | if (isActive()) { |
173 | disconnect(); //don't emit any signals due to stop() |
174 | stop(); |
175 | } |
176 | |
177 | delete d_ptr; |
178 | } |
179 | |
180 | /*! |
181 | Returns the list of all discovered services. |
182 | |
183 | This list of services accumulates newly discovered services from multiple calls |
184 | to \l start(). Unless \l clear() is called the list cannot decrease in size. This implies |
185 | that if a remote Bluetooth device moves out of range in between two subsequent calls |
186 | to \l start() the list may contain stale entries. |
187 | |
188 | \note The list of services should always be cleared before the discovery mode is changed. |
189 | |
190 | \sa clear() |
191 | */ |
192 | QList<QBluetoothServiceInfo> QBluetoothServiceDiscoveryAgent::discoveredServices() const |
193 | { |
194 | Q_D(const QBluetoothServiceDiscoveryAgent); |
195 | |
196 | return d->discoveredServices; |
197 | } |
198 | /*! |
199 | Sets the UUID filter to \a uuids. Only services matching the UUIDs in \a uuids will be |
200 | returned. The matching applies to the service's |
201 | \l {QBluetoothServiceInfo::ServiceId}{ServiceId} and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds} |
202 | attributes. |
203 | |
204 | An empty UUID list is equivalent to a list containing only QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup. |
205 | |
206 | \sa uuidFilter() |
207 | */ |
208 | void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QList<QBluetoothUuid> &uuids) |
209 | { |
210 | Q_D(QBluetoothServiceDiscoveryAgent); |
211 | |
212 | d->uuidFilter = uuids; |
213 | } |
214 | |
215 | /*! |
216 | This is an overloaded member function, provided for convenience. |
217 | |
218 | Sets the UUID filter to a list containing the single element \a uuid. |
219 | The matching applies to the service's \l {QBluetoothServiceInfo::ServiceId}{ServiceId} |
220 | and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds} |
221 | attributes. |
222 | |
223 | \sa uuidFilter() |
224 | */ |
225 | void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QBluetoothUuid &uuid) |
226 | { |
227 | Q_D(QBluetoothServiceDiscoveryAgent); |
228 | |
229 | d->uuidFilter.clear(); |
230 | d->uuidFilter.append(t: uuid); |
231 | } |
232 | |
233 | /*! |
234 | Returns the UUID filter. |
235 | |
236 | \sa setUuidFilter() |
237 | */ |
238 | QList<QBluetoothUuid> QBluetoothServiceDiscoveryAgent::uuidFilter() const |
239 | { |
240 | Q_D(const QBluetoothServiceDiscoveryAgent); |
241 | |
242 | return d->uuidFilter; |
243 | } |
244 | |
245 | /*! |
246 | Sets the remote device address to \a address. If \a address is default constructed, |
247 | services will be discovered on all contactable Bluetooth devices. A new remote |
248 | address can only be set while there is no service discovery in progress; otherwise |
249 | this function returns false. |
250 | |
251 | On some platforms the service discovery might lead to pairing requests. |
252 | Therefore it is not recommended to do service discoveries on all devices. |
253 | This function can be used to restrict the service discovery to a particular device. |
254 | |
255 | \sa remoteAddress() |
256 | */ |
257 | bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress &address) |
258 | { |
259 | if (isActive()) |
260 | return false; |
261 | if (!address.isNull()) |
262 | d_ptr->singleDevice = true; |
263 | d_ptr->deviceAddress = address; |
264 | return true; |
265 | } |
266 | |
267 | /*! |
268 | Returns the remote device address. If \l setRemoteAddress() is not called, the function |
269 | will return a default constructed \l QBluetoothAddress. |
270 | |
271 | \sa setRemoteAddress() |
272 | */ |
273 | QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const |
274 | { |
275 | if (d_ptr->singleDevice == true) |
276 | return d_ptr->deviceAddress; |
277 | else |
278 | return QBluetoothAddress(); |
279 | } |
280 | |
281 | namespace DarwinBluetooth { |
282 | |
283 | void qt_test_iobluetooth_runloop(); |
284 | |
285 | } |
286 | |
287 | |
288 | /*! |
289 | Starts service discovery. \a mode specifies the type of service discovery to perform. |
290 | |
291 | On some platforms, device discovery may lead to pairing requests. |
292 | |
293 | \sa DiscoveryMode |
294 | */ |
295 | void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode) |
296 | { |
297 | Q_D(QBluetoothServiceDiscoveryAgent); |
298 | #ifdef QT_OSX_BLUETOOTH |
299 | // Make sure we are on the right thread/have a run loop: |
300 | DarwinBluetooth::qt_test_iobluetooth_runloop(); |
301 | #endif |
302 | |
303 | if (d->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive |
304 | && d->error != InvalidBluetoothAdapterError) { |
305 | #if QT_CONFIG(bluez) |
306 | // done to avoid repeated parsing for adapter address |
307 | // on Bluez5 |
308 | d->foundHostAdapterPath.clear(); |
309 | #endif |
310 | d->setDiscoveryMode(mode); |
311 | // Clear any possible previous errors |
312 | d->error = QBluetoothServiceDiscoveryAgent::NoError; |
313 | d->errorString.clear(); |
314 | if (d->deviceAddress.isNull()) { |
315 | d->startDeviceDiscovery(); |
316 | } else { |
317 | d->discoveredDevices << QBluetoothDeviceInfo(d->deviceAddress, QString(), 0); |
318 | d->startServiceDiscovery(); |
319 | } |
320 | } |
321 | } |
322 | |
323 | /*! |
324 | Stops the service discovery process. The \l canceled() signal will be emitted once |
325 | the search has stopped. |
326 | */ |
327 | void QBluetoothServiceDiscoveryAgent::stop() |
328 | { |
329 | Q_D(QBluetoothServiceDiscoveryAgent); |
330 | |
331 | if (d->error == InvalidBluetoothAdapterError || !isActive()) |
332 | return; |
333 | |
334 | switch (d->discoveryState()) { |
335 | case QBluetoothServiceDiscoveryAgentPrivate::DeviceDiscovery: |
336 | d->stopDeviceDiscovery(); |
337 | break; |
338 | case QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery: |
339 | d->stopServiceDiscovery(); |
340 | default: |
341 | ; |
342 | } |
343 | |
344 | d->discoveredDevices.clear(); |
345 | } |
346 | |
347 | /*! |
348 | Clears the results of previous service discoveries and resets \l uuidFilter(). |
349 | This function does nothing during an ongoing service discovery (see \l isActive()). |
350 | |
351 | \sa discoveredServices() |
352 | */ |
353 | void QBluetoothServiceDiscoveryAgent::clear() |
354 | { |
355 | Q_D(QBluetoothServiceDiscoveryAgent); |
356 | |
357 | //don't clear the list while the search is ongoing |
358 | if (isActive()) |
359 | return; |
360 | |
361 | d->discoveredDevices.clear(); |
362 | d->discoveredServices.clear(); |
363 | d->uuidFilter.clear(); |
364 | } |
365 | |
366 | /*! |
367 | Returns \c true if the service discovery is currently active; otherwise returns \c false. |
368 | An active discovery can be stopped by calling \l stop(). |
369 | */ |
370 | bool QBluetoothServiceDiscoveryAgent::isActive() const |
371 | { |
372 | Q_D(const QBluetoothServiceDiscoveryAgent); |
373 | |
374 | return d->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive; |
375 | } |
376 | |
377 | /*! |
378 | Returns the type of error that last occurred. If the service discovery is done |
379 | for a single \l remoteAddress() it will return errors that occurred while trying to discover |
380 | services on that device. If the \l remoteAddress() is not set and devices are |
381 | discovered by a scan, errors during service discovery on individual |
382 | devices are not saved and no signals are emitted. In this case, errors are |
383 | fairly normal as some devices may not respond to discovery or |
384 | may no longer be in range. Such errors are suppressed. If no services |
385 | are returned, it can be assumed no services could be discovered. |
386 | |
387 | Any possible previous errors are cleared upon restarting the discovery. |
388 | */ |
389 | QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const |
390 | { |
391 | Q_D(const QBluetoothServiceDiscoveryAgent); |
392 | |
393 | return d->error; |
394 | } |
395 | |
396 | /*! |
397 | Returns a human-readable description of the last error that occurred during the |
398 | service discovery. |
399 | |
400 | \sa error(), errorOccurred() |
401 | */ |
402 | QString QBluetoothServiceDiscoveryAgent::errorString() const |
403 | { |
404 | Q_D(const QBluetoothServiceDiscoveryAgent); |
405 | return d->errorString; |
406 | } |
407 | |
408 | |
409 | /*! |
410 | \fn QBluetoothServiceDiscoveryAgent::canceled() |
411 | |
412 | This signal is triggered when the service discovery was canceled via a call to \l stop(). |
413 | */ |
414 | |
415 | |
416 | /*! |
417 | Starts device discovery. |
418 | */ |
419 | void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() |
420 | { |
421 | Q_Q(QBluetoothServiceDiscoveryAgent); |
422 | |
423 | if (!deviceDiscoveryAgent) { |
424 | #if QT_CONFIG(bluez) |
425 | deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(m_deviceAdapterAddress, q); |
426 | #else |
427 | deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(q); |
428 | #endif |
429 | QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::finished, |
430 | context: q, slot: [this](){ |
431 | this->_q_deviceDiscoveryFinished(); |
432 | }); |
433 | QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, |
434 | context: q, slot: [this](const QBluetoothDeviceInfo &info){ |
435 | this->_q_deviceDiscovered(info); |
436 | }); |
437 | QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::errorOccurred, context: q, |
438 | slot: [this](QBluetoothDeviceDiscoveryAgent::Error newError) { |
439 | this->_q_deviceDiscoveryError(newError); |
440 | }); |
441 | } |
442 | |
443 | setDiscoveryState(DeviceDiscovery); |
444 | |
445 | deviceDiscoveryAgent->start(method: QBluetoothDeviceDiscoveryAgent::ClassicMethod); |
446 | } |
447 | |
448 | /*! |
449 | Stops device discovery. |
450 | */ |
451 | void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery() |
452 | { |
453 | // disconnect to avoid recursion during stop() - QTBUG-60131 |
454 | // we don't care about a potential signals from device discovery agent anymore |
455 | deviceDiscoveryAgent->disconnect(); |
456 | |
457 | deviceDiscoveryAgent->stop(); |
458 | delete deviceDiscoveryAgent; |
459 | deviceDiscoveryAgent = nullptr; |
460 | |
461 | setDiscoveryState(Inactive); |
462 | |
463 | Q_Q(QBluetoothServiceDiscoveryAgent); |
464 | emit q->canceled(); |
465 | } |
466 | |
467 | /*! |
468 | Called when device discovery finishes. |
469 | */ |
470 | void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished() |
471 | { |
472 | if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) { |
473 | //Forward the device discovery error |
474 | error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error()); |
475 | errorString = deviceDiscoveryAgent->errorString(); |
476 | setDiscoveryState(Inactive); |
477 | Q_Q(QBluetoothServiceDiscoveryAgent); |
478 | emit q->errorOccurred(error); |
479 | emit q->finished(); |
480 | return; |
481 | } |
482 | |
483 | delete deviceDiscoveryAgent; |
484 | deviceDiscoveryAgent = nullptr; |
485 | |
486 | startServiceDiscovery(); |
487 | } |
488 | |
489 | void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info) |
490 | { |
491 | // look for duplicates, and cached entries |
492 | const auto addressEquals = [](const auto &a) { |
493 | return [a](const auto &info) { return info.address() == a; }; |
494 | }; |
495 | erase_if(list&: discoveredDevices, pred: addressEquals(info.address())); |
496 | discoveredDevices.prepend(t: info); |
497 | } |
498 | |
499 | void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error newError) |
500 | { |
501 | error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(newError); |
502 | errorString = deviceDiscoveryAgent->errorString(); |
503 | |
504 | // disconnect to avoid recursion during stop() - QTBUG-60131 |
505 | // we don't care about a potential signals from device discovery agent anymore |
506 | deviceDiscoveryAgent->disconnect(); |
507 | |
508 | deviceDiscoveryAgent->stop(); |
509 | delete deviceDiscoveryAgent; |
510 | deviceDiscoveryAgent = nullptr; |
511 | |
512 | setDiscoveryState(Inactive); |
513 | Q_Q(QBluetoothServiceDiscoveryAgent); |
514 | emit q->errorOccurred(error); |
515 | emit q->finished(); |
516 | } |
517 | |
518 | /*! |
519 | Starts service discovery for the next device. |
520 | */ |
521 | void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() |
522 | { |
523 | Q_Q(QBluetoothServiceDiscoveryAgent); |
524 | |
525 | if (discoveredDevices.isEmpty()) { |
526 | setDiscoveryState(Inactive); |
527 | emit q->finished(); |
528 | return; |
529 | } |
530 | |
531 | setDiscoveryState(ServiceDiscovery); |
532 | start(address: discoveredDevices.at(i: 0).address()); |
533 | } |
534 | |
535 | /*! |
536 | Stops service discovery. |
537 | */ |
538 | void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery() |
539 | { |
540 | stop(); |
541 | |
542 | setDiscoveryState(Inactive); |
543 | } |
544 | |
545 | void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished() |
546 | { |
547 | if(!discoveredDevices.isEmpty()) { |
548 | discoveredDevices.removeFirst(); |
549 | } |
550 | |
551 | startServiceDiscovery(); |
552 | } |
553 | |
554 | bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService( |
555 | const QBluetoothServiceInfo &serviceInfo) const |
556 | { |
557 | //check the service is not already part of our known list |
558 | for (const QBluetoothServiceInfo &info : discoveredServices) { |
559 | if (info.device() == serviceInfo.device() |
560 | && info.serviceClassUuids() == serviceInfo.serviceClassUuids() |
561 | && info.serviceUuid() == serviceInfo.serviceUuid() |
562 | && info.serverChannel() == serviceInfo.serverChannel()) { |
563 | return true; |
564 | } |
565 | } |
566 | return false; |
567 | } |
568 | |
569 | QT_END_NAMESPACE |
570 | |
571 | #include "moc_qbluetoothservicediscoveryagent.cpp" |
572 | |