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 \note On HarmonyOS the system confirms only the audio and input profiles
103 it tracks itself and never an RFCOMM service, so no serial port service
104 is discoverable by an inquiry, whichever UUID it carries. When
105 \l setRemoteAddress() restricts the discovery to one device, the UUIDs
106 \l setUuidFilter() names are reported for that device without the
107 platform having confirmed them, and the application has to establish
108 whether such a service answers by connecting. That is the only way to
109 obtain an RFCOMM service record here; a discovery which is not restricted
110 to one device reports the tracked profiles only.
111
112 \sa QBluetoothDeviceInfo::coreConfigurations()
113*/
114
115/*!
116 \fn QBluetoothServiceDiscoveryAgent::finished()
117
118 This signal is emitted when the Bluetooth service discovery completes.
119
120 Unlike the \l QBluetoothDeviceDiscoveryAgent::finished() signal this
121 signal will even be emitted when an error occurred during the service discovery. Therefore
122 it is recommended to check the \l errorOccurred() signal to evaluate the success of the
123 service discovery discovery.
124*/
125
126/*!
127 \fn void QBluetoothServiceDiscoveryAgent::errorOccurred(QBluetoothServiceDiscoveryAgent::Error
128 error)
129
130 This signal is emitted when an \a error occurs. The \a error parameter describes the error that
131 occurred.
132
133 \since 6.2
134*/
135
136/*!
137 Constructs a new QBluetoothServiceDiscoveryAgent with \a parent. The search is performed via the
138 local default Bluetooth adapter.
139*/
140QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent)
141 : QObject(parent),
142 d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, QBluetoothAddress()))
143{
144}
145
146/*!
147 Constructs a new QBluetoothServiceDiscoveryAgent for \a deviceAdapter and with \a parent.
148
149 It uses \a deviceAdapter for the service search. If \a deviceAdapter is default constructed
150 the resulting QBluetoothServiceDiscoveryAgent object will use the local default Bluetooth adapter.
151
152 If a \a deviceAdapter is specified that is not a local adapter \l error() will be set to
153 \l InvalidBluetoothAdapterError. Therefore it is recommended to test the error flag immediately after
154 using this constructor.
155
156 \note On WinRT the passed adapter address will be ignored.
157
158 \note On Android passing any \a deviceAdapter address is meaningless as Android 6.0 or later does not publish
159 the local Bluetooth address anymore. Subsequently, the passed adapter address can never be matched
160 against the local adapter address. Therefore the subsequent call to \l start() will always trigger
161 \l InvalidBluetoothAdapterError.
162
163 \sa error()
164*/
165QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent)
166 : QObject(parent),
167 d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, deviceAdapter))
168{
169 if (!deviceAdapter.isNull()) {
170 const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices();
171 for (const QBluetoothHostInfo &hostInfo : localDevices) {
172 if (hostInfo.address() == deviceAdapter)
173 return;
174 }
175 d_ptr->error = InvalidBluetoothAdapterError;
176 d_ptr->errorString = tr("Invalid Bluetooth adapter address");
177 }
178}
179
180/*!
181
182 Destructor for QBluetoothServiceDiscoveryAgent
183
184*/
185
186QBluetoothServiceDiscoveryAgent::~QBluetoothServiceDiscoveryAgent()
187{
188 if (isActive()) {
189 disconnect(); //don't emit any signals due to stop()
190 stop();
191 }
192
193 delete d_ptr;
194}
195
196/*!
197 Returns the list of all discovered services.
198
199 This list of services accumulates newly discovered services from multiple calls
200 to \l start(). Unless \l clear() is called the list cannot decrease in size. This implies
201 that if a remote Bluetooth device moves out of range in between two subsequent calls
202 to \l start() the list may contain stale entries.
203
204 \note The list of services should always be cleared before the discovery mode is changed.
205
206 \sa clear()
207*/
208QList<QBluetoothServiceInfo> QBluetoothServiceDiscoveryAgent::discoveredServices() const
209{
210 Q_D(const QBluetoothServiceDiscoveryAgent);
211
212 return d->discoveredServices;
213}
214/*!
215 Sets the UUID filter to \a uuids. Only services matching the UUIDs in \a uuids will be
216 returned. The matching applies to the service's
217 \l {QBluetoothServiceInfo::ServiceId}{ServiceId} and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds}
218 attributes.
219
220 An empty UUID list is equivalent to a list containing only QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup.
221
222 \sa uuidFilter()
223*/
224void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QList<QBluetoothUuid> &uuids)
225{
226 Q_D(QBluetoothServiceDiscoveryAgent);
227
228 d->uuidFilter = uuids;
229}
230
231/*!
232 This is an overloaded member function, provided for convenience.
233
234 Sets the UUID filter to a list containing the single element \a uuid.
235 The matching applies to the service's \l {QBluetoothServiceInfo::ServiceId}{ServiceId}
236 and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds}
237 attributes.
238
239 \sa uuidFilter()
240*/
241void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QBluetoothUuid &uuid)
242{
243 Q_D(QBluetoothServiceDiscoveryAgent);
244
245 d->uuidFilter.clear();
246 d->uuidFilter.append(uuid);
247}
248
249/*!
250 Returns the UUID filter.
251
252 \sa setUuidFilter()
253*/
254QList<QBluetoothUuid> QBluetoothServiceDiscoveryAgent::uuidFilter() const
255{
256 Q_D(const QBluetoothServiceDiscoveryAgent);
257
258 return d->uuidFilter;
259}
260
261/*!
262 Sets the remote device address to \a address. If \a address is default constructed,
263 services will be discovered on all contactable Bluetooth devices. A new remote
264 address can only be set while there is no service discovery in progress; otherwise
265 this function returns false.
266
267 On some platforms the service discovery might lead to pairing requests.
268 Therefore it is not recommended to do service discoveries on all devices.
269 This function can be used to restrict the service discovery to a particular device.
270
271 \sa remoteAddress()
272*/
273bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress &address)
274{
275 if (isActive())
276 return false;
277 if (!address.isNull())
278 d_ptr->singleDevice = true;
279 d_ptr->deviceAddress = address;
280 return true;
281}
282
283/*!
284 Returns the remote device address. If \l setRemoteAddress() is not called, the function
285 will return a default constructed \l QBluetoothAddress.
286
287 \sa setRemoteAddress()
288*/
289QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const
290{
291 if (d_ptr->singleDevice == true)
292 return d_ptr->deviceAddress;
293 else
294 return QBluetoothAddress();
295}
296
297namespace DarwinBluetooth {
298
300
301}
302
303
304/*!
305 Starts service discovery. \a mode specifies the type of service discovery to perform.
306
307 On some platforms, device discovery may lead to pairing requests.
308
309 \sa DiscoveryMode
310*/
311void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode)
312{
313 Q_D(QBluetoothServiceDiscoveryAgent);
314#ifdef QT_OSX_BLUETOOTH
315 // Make sure we are on the right thread/have a run loop:
316 DarwinBluetooth::qt_test_iobluetooth_runloop();
317#endif
318
319 if (d->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive
320 && d->error != InvalidBluetoothAdapterError) {
321#if QT_CONFIG(bluez)
322 // done to avoid repeated parsing for adapter address
323 // on Bluez5
324 d->foundHostAdapterPath.clear();
325#endif
326 d->setDiscoveryMode(mode);
327 // Clear any possible previous errors
328 d->error = QBluetoothServiceDiscoveryAgent::NoError;
329 d->errorString.clear();
330 if (d->deviceAddress.isNull()) {
331 d->startDeviceDiscovery();
332 } else {
333 d->discoveredDevices << QBluetoothDeviceInfo(d->deviceAddress, QString(), 0);
334 d->startServiceDiscovery();
335 }
336 }
337}
338
339/*!
340 Stops the service discovery process. The \l canceled() signal will be emitted once
341 the search has stopped.
342*/
343void QBluetoothServiceDiscoveryAgent::stop()
344{
345 Q_D(QBluetoothServiceDiscoveryAgent);
346
347 if (d->error == InvalidBluetoothAdapterError || !isActive())
348 return;
349
350 switch (d->discoveryState()) {
351 case QBluetoothServiceDiscoveryAgentPrivate::DeviceDiscovery:
352 d->stopDeviceDiscovery();
353 break;
354 case QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery:
355 d->stopServiceDiscovery();
356 break;
357 default:
358 break;
359 }
360
361 d->discoveredDevices.clear();
362}
363
364/*!
365 Clears the results of previous service discoveries and resets \l uuidFilter().
366 This function does nothing during an ongoing service discovery (see \l isActive()).
367
368 \sa discoveredServices()
369*/
370void QBluetoothServiceDiscoveryAgent::clear()
371{
372 Q_D(QBluetoothServiceDiscoveryAgent);
373
374 //don't clear the list while the search is ongoing
375 if (isActive())
376 return;
377
378 d->discoveredDevices.clear();
379 d->discoveredServices.clear();
380 d->uuidFilter.clear();
381}
382
383/*!
384 Returns \c true if the service discovery is currently active; otherwise returns \c false.
385 An active discovery can be stopped by calling \l stop().
386*/
387bool QBluetoothServiceDiscoveryAgent::isActive() const
388{
389 Q_D(const QBluetoothServiceDiscoveryAgent);
390
391 return d->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive;
392}
393
394/*!
395 Returns the type of error that last occurred. If the service discovery is done
396 for a single \l remoteAddress() it will return errors that occurred while trying to discover
397 services on that device. If the \l remoteAddress() is not set and devices are
398 discovered by a scan, errors during service discovery on individual
399 devices are not saved and no signals are emitted. In this case, errors are
400 fairly normal as some devices may not respond to discovery or
401 may no longer be in range. Such errors are suppressed. If no services
402 are returned, it can be assumed no services could be discovered.
403
404 Any possible previous errors are cleared upon restarting the discovery.
405*/
406QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const
407{
408 Q_D(const QBluetoothServiceDiscoveryAgent);
409
410 return d->error;
411}
412
413/*!
414 Returns a human-readable description of the last error that occurred during the
415 service discovery.
416
417 \sa error(), errorOccurred()
418*/
419QString QBluetoothServiceDiscoveryAgent::errorString() const
420{
421 Q_D(const QBluetoothServiceDiscoveryAgent);
422 return d->errorString;
423}
424
425
426/*!
427 \fn QBluetoothServiceDiscoveryAgent::canceled()
428
429 This signal is triggered when the service discovery was canceled via a call to \l stop().
430 */
431
432
433/*!
434 Starts device discovery.
435*/
436void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery()
437{
438 Q_Q(QBluetoothServiceDiscoveryAgent);
439
440 if (!deviceDiscoveryAgent) {
441#if QT_CONFIG(bluez)
442 deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(m_deviceAdapterAddress, q);
443#else
444 deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(q);
445#endif
446 QObject::connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
447 q, [this](){
448 this->_q_deviceDiscoveryFinished();
449 });
450 QObject::connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
451 q, [this](const QBluetoothDeviceInfo &info){
452 this->_q_deviceDiscovered(info);
453 });
454 QObject::connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred, q,
455 [this](QBluetoothDeviceDiscoveryAgent::Error newError) {
456 this->_q_deviceDiscoveryError(newError);
457 });
458 }
459
460 setDiscoveryState(DeviceDiscovery);
461
462 deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
463}
464
465/*!
466 Stops device discovery.
467*/
468void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery()
469{
470 // disconnect to avoid recursion during stop() - QTBUG-60131
471 // we don't care about a potential signals from device discovery agent anymore
472 deviceDiscoveryAgent->disconnect();
473
474 deviceDiscoveryAgent->stop();
475 delete deviceDiscoveryAgent;
476 deviceDiscoveryAgent = nullptr;
477
478 setDiscoveryState(Inactive);
479
480 Q_Q(QBluetoothServiceDiscoveryAgent);
481 emit q->canceled();
482}
483
484/*!
485 Called when device discovery finishes.
486*/
487void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished()
488{
489 if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) {
490 //Forward the device discovery error
491 error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error());
492 errorString = deviceDiscoveryAgent->errorString();
493 setDiscoveryState(Inactive);
494 Q_Q(QBluetoothServiceDiscoveryAgent);
495 emit q->errorOccurred(error);
496 emit q->finished();
497 return;
498 }
499
500 delete deviceDiscoveryAgent;
501 deviceDiscoveryAgent = nullptr;
502
503 startServiceDiscovery();
504}
505
506void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info)
507{
508 // look for duplicates, and cached entries
509 const auto addressEquals = [](const auto &a) {
510 return [a](const auto &info) { return info.address() == a; };
511 };
512 erase_if(discoveredDevices, addressEquals(info.address()));
513 discoveredDevices.prepend(info);
514}
515
516void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error newError)
517{
518 error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(newError);
519 errorString = deviceDiscoveryAgent->errorString();
520
521 // disconnect to avoid recursion during stop() - QTBUG-60131
522 // we don't care about a potential signals from device discovery agent anymore
523 deviceDiscoveryAgent->disconnect();
524
525 deviceDiscoveryAgent->stop();
526 delete deviceDiscoveryAgent;
527 deviceDiscoveryAgent = nullptr;
528
529 setDiscoveryState(Inactive);
530 Q_Q(QBluetoothServiceDiscoveryAgent);
531 emit q->errorOccurred(error);
532 emit q->finished();
533}
534
535/*!
536 Starts service discovery for the next device.
537*/
538void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery()
539{
540 Q_Q(QBluetoothServiceDiscoveryAgent);
541
542 if (discoveredDevices.isEmpty()) {
543 setDiscoveryState(Inactive);
544 emit q->finished();
545 return;
546 }
547
548 setDiscoveryState(ServiceDiscovery);
549 start(discoveredDevices.at(0).address());
550}
551
552/*!
553 Stops service discovery.
554*/
555void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery()
556{
557 stop();
558
559 setDiscoveryState(Inactive);
560}
561
562void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished()
563{
564 if(!discoveredDevices.isEmpty()) {
565 discoveredDevices.removeFirst();
566 }
567
568 startServiceDiscovery();
569}
570
571bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService(
572 const QBluetoothServiceInfo &serviceInfo) const
573{
574 //check the service is not already part of our known list
575 for (const QBluetoothServiceInfo &info : discoveredServices) {
576 if (info.device() == serviceInfo.device()
577 && info.serviceClassUuids() == serviceInfo.serviceClassUuids()
578 && info.serviceUuid() == serviceInfo.serviceUuid()
579 && info.serverChannel() == serviceInfo.serverChannel()) {
580 return true;
581 }
582 }
583 return false;
584}
585
586QT_END_NAMESPACE
587
588#include "moc_qbluetoothservicediscoveryagent.cpp"
void qt_test_iobluetooth_runloop()
Definition btutility.mm:125