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 break;
341 default:
342 break;
343 }
344
345 d->discoveredDevices.clear();
346}
347
348/*!
349 Clears the results of previous service discoveries and resets \l uuidFilter().
350 This function does nothing during an ongoing service discovery (see \l isActive()).
351
352 \sa discoveredServices()
353*/
354void QBluetoothServiceDiscoveryAgent::clear()
355{
356 Q_D(QBluetoothServiceDiscoveryAgent);
357
358 //don't clear the list while the search is ongoing
359 if (isActive())
360 return;
361
362 d->discoveredDevices.clear();
363 d->discoveredServices.clear();
364 d->uuidFilter.clear();
365}
366
367/*!
368 Returns \c true if the service discovery is currently active; otherwise returns \c false.
369 An active discovery can be stopped by calling \l stop().
370*/
371bool QBluetoothServiceDiscoveryAgent::isActive() const
372{
373 Q_D(const QBluetoothServiceDiscoveryAgent);
374
375 return d->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive;
376}
377
378/*!
379 Returns the type of error that last occurred. If the service discovery is done
380 for a single \l remoteAddress() it will return errors that occurred while trying to discover
381 services on that device. If the \l remoteAddress() is not set and devices are
382 discovered by a scan, errors during service discovery on individual
383 devices are not saved and no signals are emitted. In this case, errors are
384 fairly normal as some devices may not respond to discovery or
385 may no longer be in range. Such errors are suppressed. If no services
386 are returned, it can be assumed no services could be discovered.
387
388 Any possible previous errors are cleared upon restarting the discovery.
389*/
390QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const
391{
392 Q_D(const QBluetoothServiceDiscoveryAgent);
393
394 return d->error;
395}
396
397/*!
398 Returns a human-readable description of the last error that occurred during the
399 service discovery.
400
401 \sa error(), errorOccurred()
402*/
403QString QBluetoothServiceDiscoveryAgent::errorString() const
404{
405 Q_D(const QBluetoothServiceDiscoveryAgent);
406 return d->errorString;
407}
408
409
410/*!
411 \fn QBluetoothServiceDiscoveryAgent::canceled()
412
413 This signal is triggered when the service discovery was canceled via a call to \l stop().
414 */
415
416
417/*!
418 Starts device discovery.
419*/
420void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery()
421{
422 Q_Q(QBluetoothServiceDiscoveryAgent);
423
424 if (!deviceDiscoveryAgent) {
425#if QT_CONFIG(bluez)
426 deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(m_deviceAdapterAddress, q);
427#else
428 deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(q);
429#endif
430 QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::finished,
431 context: q, slot: [this](){
432 this->_q_deviceDiscoveryFinished();
433 });
434 QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
435 context: q, slot: [this](const QBluetoothDeviceInfo &info){
436 this->_q_deviceDiscovered(info);
437 });
438 QObject::connect(sender: deviceDiscoveryAgent, signal: &QBluetoothDeviceDiscoveryAgent::errorOccurred, context: q,
439 slot: [this](QBluetoothDeviceDiscoveryAgent::Error newError) {
440 this->_q_deviceDiscoveryError(newError);
441 });
442 }
443
444 setDiscoveryState(DeviceDiscovery);
445
446 deviceDiscoveryAgent->start(method: QBluetoothDeviceDiscoveryAgent::ClassicMethod);
447}
448
449/*!
450 Stops device discovery.
451*/
452void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery()
453{
454 // disconnect to avoid recursion during stop() - QTBUG-60131
455 // we don't care about a potential signals from device discovery agent anymore
456 deviceDiscoveryAgent->disconnect();
457
458 deviceDiscoveryAgent->stop();
459 delete deviceDiscoveryAgent;
460 deviceDiscoveryAgent = nullptr;
461
462 setDiscoveryState(Inactive);
463
464 Q_Q(QBluetoothServiceDiscoveryAgent);
465 emit q->canceled();
466}
467
468/*!
469 Called when device discovery finishes.
470*/
471void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished()
472{
473 if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) {
474 //Forward the device discovery error
475 error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error());
476 errorString = deviceDiscoveryAgent->errorString();
477 setDiscoveryState(Inactive);
478 Q_Q(QBluetoothServiceDiscoveryAgent);
479 emit q->errorOccurred(error);
480 emit q->finished();
481 return;
482 }
483
484 delete deviceDiscoveryAgent;
485 deviceDiscoveryAgent = nullptr;
486
487 startServiceDiscovery();
488}
489
490void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info)
491{
492 // look for duplicates, and cached entries
493 const auto addressEquals = [](const auto &a) {
494 return [a](const auto &info) { return info.address() == a; };
495 };
496 erase_if(list&: discoveredDevices, pred: addressEquals(info.address()));
497 discoveredDevices.prepend(t: info);
498}
499
500void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error newError)
501{
502 error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(newError);
503 errorString = deviceDiscoveryAgent->errorString();
504
505 // disconnect to avoid recursion during stop() - QTBUG-60131
506 // we don't care about a potential signals from device discovery agent anymore
507 deviceDiscoveryAgent->disconnect();
508
509 deviceDiscoveryAgent->stop();
510 delete deviceDiscoveryAgent;
511 deviceDiscoveryAgent = nullptr;
512
513 setDiscoveryState(Inactive);
514 Q_Q(QBluetoothServiceDiscoveryAgent);
515 emit q->errorOccurred(error);
516 emit q->finished();
517}
518
519/*!
520 Starts service discovery for the next device.
521*/
522void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery()
523{
524 Q_Q(QBluetoothServiceDiscoveryAgent);
525
526 if (discoveredDevices.isEmpty()) {
527 setDiscoveryState(Inactive);
528 emit q->finished();
529 return;
530 }
531
532 setDiscoveryState(ServiceDiscovery);
533 start(address: discoveredDevices.at(i: 0).address());
534}
535
536/*!
537 Stops service discovery.
538*/
539void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery()
540{
541 stop();
542
543 setDiscoveryState(Inactive);
544}
545
546void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished()
547{
548 if(!discoveredDevices.isEmpty()) {
549 discoveredDevices.removeFirst();
550 }
551
552 startServiceDiscovery();
553}
554
555bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService(
556 const QBluetoothServiceInfo &serviceInfo) const
557{
558 //check the service is not already part of our known list
559 for (const QBluetoothServiceInfo &info : discoveredServices) {
560 if (info.device() == serviceInfo.device()
561 && info.serviceClassUuids() == serviceInfo.serviceClassUuids()
562 && info.serviceUuid() == serviceInfo.serviceUuid()
563 && info.serverChannel() == serviceInfo.serverChannel()) {
564 return true;
565 }
566 }
567 return false;
568}
569
570QT_END_NAMESPACE
571
572#include "moc_qbluetoothservicediscoveryagent.cpp"
573

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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