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