| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtBluetooth module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qbluetoothhostinfo.h" |
| #include "qbluetoothlocaldevice.h" |
| #include "qbluetoothservicediscoveryagent.h" |
| #include "qbluetoothservicediscoveryagent_p.h" |
| |
| #include "qbluetoothdevicediscoveryagent.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \class QBluetoothServiceDiscoveryAgent |
| \inmodule QtBluetooth |
| \brief The QBluetoothServiceDiscoveryAgent class enables you to query for |
| Bluetooth services. |
| |
| \since 5.2 |
| |
| The discovery process relies on the Bluetooth Service Discovery Process (SDP). |
| The following steps are required to query the services provided by all contactable |
| Bluetooth devices: |
| |
| \list |
| \li create an instance of QBluetoothServiceDiscoveryAgent, |
| \li connect to either the serviceDiscovered() or finished() signals, |
| \li and call start(). |
| \endlist |
| |
| \snippet doc_src_qtbluetooth.cpp service_discovery |
| |
| By default a minimal service discovery is performed. In this mode, the returned \l QBluetoothServiceInfo |
| objects are guaranteed to contain only device and service UUID information. Depending |
| on platform and device capabilities, other service information may also be available. |
| The minimal service discovery mode relies on cached SDP data of the platform. Therefore it |
| is possible that this discovery does not find a device although it is physically available. |
| In such cases a full discovery must be performed to force an update of the platform cache. |
| However for most use cases a minimal discovery is adequate as it is much quicker and other |
| classes which require up-to-date information such as QBluetoothSocket::connectToService() |
| will perform additional discovery if required. If the full service information is required, |
| pass \l FullDiscovery as the discoveryMode parameter to start(). |
| |
| This class may internally utilize \l QBluetoothDeviceDiscoveryAgent to find unknown devices. |
| |
| The service discovery may find Bluetooth Low Energy services too if the target device |
| is a combination of a classic and Low Energy device. Those devices are required to advertise |
| their Low Energy services via SDP. If the target device only supports Bluetooth Low |
| Energy services, it is likely to not advertise them via SDP. The \l QLowEnergyController class |
| should be utilized to perform the service discovery on Low Energy devices. |
| |
| On iOS, this class cannot be used because the platform does not expose |
| an API which may permit access to QBluetoothServiceDiscoveryAgent related features. |
| |
| \sa QBluetoothDeviceDiscoveryAgent, QLowEnergyController |
| */ |
| |
| /*! |
| \enum QBluetoothServiceDiscoveryAgent::Error |
| |
| This enum describes errors that can occur during service discovery. |
| |
| \value NoError No error has occurred. |
| \value PoweredOffError The Bluetooth adaptor is powered off, power it on before doing discovery. |
| \value InputOutputError Writing or reading from the device resulted in an error. |
| \value InvalidBluetoothAdapterError The passed local adapter address does not match the physical |
| adapter address of any local Bluetooth device. This value |
| was introduced by Qt 5.3. |
| \value UnknownError An unknown error has occurred. |
| */ |
| |
| /*! |
| \enum QBluetoothServiceDiscoveryAgent::DiscoveryMode |
| |
| This enum describes the service discovery mode. |
| |
| \value MinimalDiscovery Performs a minimal service discovery. The QBluetoothServiceInfo |
| objects returned may be incomplete and are only guaranteed to contain device and service UUID information. |
| Since a minimal discovery relies on cached SDP data it may not find a physically existing |
| device until a \c FullDiscovery is performed. |
| \value FullDiscovery Performs a full service discovery. |
| */ |
| |
| /*! |
| \fn QBluetoothServiceDiscoveryAgent::serviceDiscovered(const QBluetoothServiceInfo &info) |
| |
| This signal is emitted when the Bluetooth service described by \a info is discovered. |
| |
| \note The passed \l QBluetoothServiceInfo parameter may contain a Bluetooth Low Energy |
| service if the target device advertises the service via SDP. This is required from device |
| which support both, classic Bluetooth (BaseRate) and Low Energy services. |
| |
| \sa QBluetoothDeviceInfo::coreConfigurations() |
| */ |
| |
| /*! |
| \fn QBluetoothServiceDiscoveryAgent::finished() |
| |
| This signal is emitted when the Bluetooth service discovery completes. |
| |
| Unlike the \l QBluetoothDeviceDiscoveryAgent::finished() signal this |
| signal will even be emitted when an error occurred during the service discovery. Therefore |
| it is recommended to check the \l error() signal to evaluate the success of the |
| service discovery discovery. |
| */ |
| |
| /*! |
| \fn void QBluetoothServiceDiscoveryAgent::error(QBluetoothServiceDiscoveryAgent::Error error) |
| |
| This signal is emitted when an \a error occurs. The \a error parameter describes the error that |
| occurred. |
| */ |
| |
| /*! |
| Constructs a new QBluetoothServiceDiscoveryAgent with \a parent. The search is performed via the |
| local default Bluetooth adapter. |
| */ |
| QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent) |
| : QObject(parent), |
| d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, QBluetoothAddress())) |
| { |
| } |
| |
| /*! |
| Constructs a new QBluetoothServiceDiscoveryAgent for \a deviceAdapter and with \a parent. |
| |
| It uses \a deviceAdapter for the service search. If \a deviceAdapter is default constructed |
| the resulting QBluetoothServiceDiscoveryAgent object will use the local default Bluetooth adapter. |
| |
| If a \a deviceAdapter is specified that is not a local adapter \l error() will be set to |
| \l InvalidBluetoothAdapterError. Therefore it is recommended to test the error flag immediately after |
| using this constructor. |
| |
| \note On WinRT the passed adapter address will be ignored. |
| |
| \note On Android passing any \a deviceAdapter address is meaningless as Android 6.0 or later does not publish |
| the local Bluetooth address anymore. Subsequently, the passed adapter address can never be matched |
| against the local adapter address. Therefore the subsequent call to \l start() will always trigger |
| \l InvalidBluetoothAdapterError. |
| |
| \sa error() |
| */ |
| QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent) |
| : QObject(parent), |
| d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(this, deviceAdapter)) |
| { |
| if (!deviceAdapter.isNull()) { |
| const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices(); |
| for (const QBluetoothHostInfo &hostInfo : localDevices) { |
| if (hostInfo.address() == deviceAdapter) |
| return; |
| } |
| d_ptr->error = InvalidBluetoothAdapterError; |
| d_ptr->errorString = tr("Invalid Bluetooth adapter address"); |
| } |
| } |
| |
| /*! |
| |
| Destructor for QBluetoothServiceDiscoveryAgent |
| |
| */ |
| |
| QBluetoothServiceDiscoveryAgent::~QBluetoothServiceDiscoveryAgent() |
| { |
| if (isActive()) { |
| disconnect(); //don't emit any signals due to stop() |
| stop(); |
| } |
| |
| delete d_ptr; |
| } |
| |
| /*! |
| Returns the list of all discovered services. |
| |
| This list of services accumulates newly discovered services from multiple calls |
| to \l start(). Unless \l clear() is called the list cannot decrease in size. This implies |
| that if a remote Bluetooth device moves out of range in between two subsequent calls |
| to \l start() the list may contain stale entries. |
| |
| \note The list of services should always be cleared before the discovery mode is changed. |
| |
| \sa clear() |
| */ |
| QList<QBluetoothServiceInfo> QBluetoothServiceDiscoveryAgent::discoveredServices() const |
| { |
| Q_D(const QBluetoothServiceDiscoveryAgent); |
| |
| return d->discoveredServices; |
| } |
| /*! |
| Sets the UUID filter to \a uuids. Only services matching the UUIDs in \a uuids will be |
| returned. The matching applies to the service's |
| \l {QBluetoothServiceInfo::ServiceId}{ServiceId} and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds} |
| attributes. |
| |
| An empty UUID list is equivalent to a list containing only QBluetoothUuid::PublicBrowseGroup. |
| |
| \sa uuidFilter() |
| */ |
| void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QList<QBluetoothUuid> &uuids) |
| { |
| Q_D(QBluetoothServiceDiscoveryAgent); |
| |
| d->uuidFilter = uuids; |
| } |
| |
| /*! |
| This is an overloaded member function, provided for convenience. |
| |
| Sets the UUID filter to a list containing the single element \a uuid. |
| The matching applies to the service's \l {QBluetoothServiceInfo::ServiceId}{ServiceId} |
| and \l {QBluetoothServiceInfo::ServiceClassIds} {ServiceClassIds} |
| attributes. |
| |
| \sa uuidFilter() |
| */ |
| void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QBluetoothUuid &uuid) |
| { |
| Q_D(QBluetoothServiceDiscoveryAgent); |
| |
| d->uuidFilter.clear(); |
| d->uuidFilter.append(uuid); |
| } |
| |
| /*! |
| Returns the UUID filter. |
| |
| \sa setUuidFilter() |
| */ |
| QList<QBluetoothUuid> QBluetoothServiceDiscoveryAgent::uuidFilter() const |
| { |
| Q_D(const QBluetoothServiceDiscoveryAgent); |
| |
| return d->uuidFilter; |
| } |
| |
| /*! |
| Sets the remote device address to \a address. If \a address is default constructed, |
| services will be discovered on all contactable Bluetooth devices. A new remote |
| address can only be set while there is no service discovery in progress; otherwise |
| this function returns false. |
| |
| On some platforms the service discovery might lead to pairing requests. |
| Therefore it is not recommended to do service discoveries on all devices. |
| This function can be used to restrict the service discovery to a particular device. |
| |
| \sa remoteAddress() |
| */ |
| bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress &address) |
| { |
| if (isActive()) |
| return false; |
| if (!address.isNull()) |
| d_ptr->singleDevice = true; |
| d_ptr->deviceAddress = address; |
| return true; |
| } |
| |
| /*! |
| Returns the remote device address. If \l setRemoteAddress() is not called, the function |
| will return a default constructed \l QBluetoothAddress. |
| |
| \sa setRemoteAddress() |
| */ |
| QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const |
| { |
| if (d_ptr->singleDevice == true) |
| return d_ptr->deviceAddress; |
| else |
| return QBluetoothAddress(); |
| } |
| |
| namespace OSXBluetooth { |
| |
| void qt_test_iobluetooth_runloop(); |
| |
| } |
| |
| |
| /*! |
| Starts service discovery. \a mode specifies the type of service discovery to perform. |
| |
| On some platforms, device discovery may lead to pairing requests. |
| |
| \sa DiscoveryMode |
| */ |
| void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode) |
| { |
| Q_D(QBluetoothServiceDiscoveryAgent); |
| #ifdef QT_OSX_BLUETOOTH |
| // Make sure we are on the right thread/have a run loop: |
| OSXBluetooth::qt_test_iobluetooth_runloop(); |
| #endif |
| |
| if (d->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive |
| && d->error != InvalidBluetoothAdapterError) { |
| #if QT_CONFIG(bluez) |
| // done to avoid repeated parsing for adapter address |
| // on Bluez5 |
| d->foundHostAdapterPath.clear(); |
| #endif |
| d->setDiscoveryMode(mode); |
| if (d->deviceAddress.isNull()) { |
| d->startDeviceDiscovery(); |
| } else { |
| d->discoveredDevices << QBluetoothDeviceInfo(d->deviceAddress, QString(), 0); |
| d->startServiceDiscovery(); |
| } |
| } |
| } |
| |
| /*! |
| Stops the service discovery process. The \l canceled() signal will be emitted once |
| the search has stopped. |
| */ |
| void QBluetoothServiceDiscoveryAgent::stop() |
| { |
| Q_D(QBluetoothServiceDiscoveryAgent); |
| |
| if (d->error == InvalidBluetoothAdapterError || !isActive()) |
| return; |
| |
| switch (d->discoveryState()) { |
| case QBluetoothServiceDiscoveryAgentPrivate::DeviceDiscovery: |
| d->stopDeviceDiscovery(); |
| break; |
| case QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery: |
| d->stopServiceDiscovery(); |
| default: |
| ; |
| } |
| |
| d->discoveredDevices.clear(); |
| } |
| |
| /*! |
| Clears the results of previous service discoveries and resets \l uuidFilter(). |
| This function does nothing during an ongoing service discovery (see \l isActive()). |
| |
| \sa discoveredServices() |
| */ |
| void QBluetoothServiceDiscoveryAgent::clear() |
| { |
| Q_D(QBluetoothServiceDiscoveryAgent); |
| |
| //don't clear the list while the search is ongoing |
| if (isActive()) |
| return; |
| |
| d->discoveredDevices.clear(); |
| d->discoveredServices.clear(); |
| d->uuidFilter.clear(); |
| } |
| |
| /*! |
| Returns \c true if the service discovery is currently active; otherwise returns \c false. |
| An active discovery can be stopped by calling \l stop(). |
| */ |
| bool QBluetoothServiceDiscoveryAgent::isActive() const |
| { |
| Q_D(const QBluetoothServiceDiscoveryAgent); |
| |
| return d->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive; |
| } |
| |
| /*! |
| Returns the type of error that last occurred. If the service discovery is done |
| for a single \l remoteAddress() it will return errors that occurred while trying to discover |
| services on that device. If the \l remoteAddress() is not set and devices are |
| discovered by a scan, errors during service discovery on individual |
| devices are not saved and no signals are emitted. In this case, errors are |
| fairly normal as some devices may not respond to discovery or |
| may no longer be in range. Such errors are surpressed. If no services |
| are returned, it can be assumed no services could be discovered. |
| |
| */ |
| QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const |
| { |
| Q_D(const QBluetoothServiceDiscoveryAgent); |
| |
| return d->error; |
| } |
| |
| /*! |
| Returns a human-readable description of the last error that occurred during the |
| service discovery. |
| */ |
| QString QBluetoothServiceDiscoveryAgent::errorString() const |
| { |
| Q_D(const QBluetoothServiceDiscoveryAgent); |
| return d->errorString; |
| } |
| |
| |
| /*! |
| \fn QBluetoothServiceDiscoveryAgent::canceled() |
| |
| This signal is triggered when the service discovery was canceled via a call to \l stop(). |
| */ |
| |
| |
| /*! |
| Starts device discovery. |
| */ |
| void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() |
| { |
| Q_Q(QBluetoothServiceDiscoveryAgent); |
| |
| if (!deviceDiscoveryAgent) { |
| #if QT_CONFIG(bluez) |
| deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(m_deviceAdapterAddress, q); |
| #else |
| deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(q); |
| #endif |
| QObject::connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, |
| q, [this](){ |
| this->_q_deviceDiscoveryFinished(); |
| }); |
| QObject::connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, |
| q, [this](const QBluetoothDeviceInfo &info){ |
| this->_q_deviceDiscovered(info); |
| }); |
| QObject::connect(deviceDiscoveryAgent, |
| QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error), |
| q, [this](QBluetoothDeviceDiscoveryAgent::Error newError){ |
| this->_q_deviceDiscoveryError(newError); |
| }); |
| } |
| |
| setDiscoveryState(DeviceDiscovery); |
| |
| deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); |
| } |
| |
| /*! |
| Stops device discovery. |
| */ |
| void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery() |
| { |
| // disconnect to avoid recursion during stop() - QTBUG-60131 |
| // we don't care about a potential signals from device discovery agent anymore |
| deviceDiscoveryAgent->disconnect(); |
| |
| deviceDiscoveryAgent->stop(); |
| delete deviceDiscoveryAgent; |
| deviceDiscoveryAgent = nullptr; |
| |
| setDiscoveryState(Inactive); |
| |
| Q_Q(QBluetoothServiceDiscoveryAgent); |
| emit q->canceled(); |
| } |
| |
| /*! |
| Called when device discovery finishes. |
| */ |
| void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished() |
| { |
| if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) { |
| //Forward the device discovery error |
| error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error()); |
| errorString = deviceDiscoveryAgent->errorString(); |
| setDiscoveryState(Inactive); |
| Q_Q(QBluetoothServiceDiscoveryAgent); |
| emit q->error(error); |
| emit q->finished(); |
| return; |
| } |
| |
| delete deviceDiscoveryAgent; |
| deviceDiscoveryAgent = nullptr; |
| |
| startServiceDiscovery(); |
| } |
| |
| void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info) |
| { |
| // look for duplicates, and cached entries |
| for (int i = 0; i < discoveredDevices.count(); i++) { |
| if (discoveredDevices.at(i).address() == info.address()) |
| discoveredDevices.removeAt(i); |
| } |
| discoveredDevices.prepend(info); |
| } |
| |
| void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error newError) |
| { |
| error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(newError); |
| errorString = deviceDiscoveryAgent->errorString(); |
| |
| // disconnect to avoid recursion during stop() - QTBUG-60131 |
| // we don't care about a potential signals from device discovery agent anymore |
| deviceDiscoveryAgent->disconnect(); |
| |
| deviceDiscoveryAgent->stop(); |
| delete deviceDiscoveryAgent; |
| deviceDiscoveryAgent = nullptr; |
| |
| setDiscoveryState(Inactive); |
| Q_Q(QBluetoothServiceDiscoveryAgent); |
| emit q->error(error); |
| emit q->finished(); |
| } |
| |
| /*! |
| Starts service discovery for the next device. |
| */ |
| void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() |
| { |
| Q_Q(QBluetoothServiceDiscoveryAgent); |
| |
| if (discoveredDevices.isEmpty()) { |
| setDiscoveryState(Inactive); |
| emit q->finished(); |
| return; |
| } |
| |
| setDiscoveryState(ServiceDiscovery); |
| start(discoveredDevices.at(0).address()); |
| } |
| |
| /*! |
| Stops service discovery. |
| */ |
| void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery() |
| { |
| stop(); |
| |
| setDiscoveryState(Inactive); |
| } |
| |
| void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished() |
| { |
| if(!discoveredDevices.isEmpty()) { |
| discoveredDevices.removeFirst(); |
| } |
| |
| startServiceDiscovery(); |
| } |
| |
| bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService( |
| const QBluetoothServiceInfo &serviceInfo) const |
| { |
| //check the service is not already part of our known list |
| for (int j = 0; j < discoveredServices.count(); j++) { |
| const QBluetoothServiceInfo &info = discoveredServices.at(j); |
| if (info.device() == serviceInfo.device() |
| && info.serviceClassUuids() == serviceInfo.serviceClassUuids() |
| && info.serviceUuid() == serviceInfo.serviceUuid() |
| && info.serverChannel() == serviceInfo.serverChannel()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qbluetoothservicediscoveryagent.cpp" |