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