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
11QT_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*/
124QBluetoothServiceDiscoveryAgent::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*/
149QBluetoothServiceDiscoveryAgent::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
170QBluetoothServiceDiscoveryAgent::~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*/
192QList<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*/
208void 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*/
225void 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*/
238QList<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*/
257bool 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*/
273QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const
274{
275 if (d_ptr->singleDevice == true)
276 return d_ptr->deviceAddress;
277 else
278 return QBluetoothAddress();
279}
280
281namespace DarwinBluetooth {
282
283void 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*/
295void 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*/
327void 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*/
353void 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*/
370bool 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*/
389QBluetoothServiceDiscoveryAgent::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*/
402QString 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*/
419void 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*/
451void 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*/
470void 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
489void 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
499void 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*/
521void 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*/
538void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery()
539{
540 stop();
541
542 setDiscoveryState(Inactive);
543}
544
545void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished()
546{
547 if(!discoveredDevices.isEmpty()) {
548 discoveredDevices.removeFirst();
549 }
550
551 startServiceDiscovery();
552}
553
554bool 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
569QT_END_NAMESPACE
570
571#include "moc_qbluetoothservicediscoveryagent.cpp"
572

source code of qtconnectivity/src/bluetooth/qbluetoothservicediscoveryagent.cpp