Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qbluetoothservicediscoveryagent_android.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
10
11#include <QCoreApplication>
12#include <QtCore/qcoreapplication.h>
13#include <QtCore/QLoggingCategory>
14#include <QtCore/QTimer>
15#include <QtCore/QJniEnvironment>
16#include <QtBluetooth/QBluetoothHostInfo>
17#include <QtBluetooth/QBluetoothLocalDevice>
18#include <QtBluetooth/QBluetoothServiceDiscoveryAgent>
19
21
22Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
23
24static constexpr auto uuidFetchTimeLimit = std::chrono::seconds{4};
25
27 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
29 m_deviceAdapterAddress(deviceAdapter),
30 state(Inactive),
31 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
32 singleDevice(false),
33 q_ptr(qp)
34
35{
36 // If a specific adapter address is requested we need to check it matches
37 // the current local adapter. If it does not match we emit
38 // InvalidBluetoothAdapterError when calling start()
39
40 bool createAdapter = true;
41 if (!deviceAdapter.isNull()) {
42 const QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices();
43 if (devices.isEmpty()) {
44 createAdapter = false;
45 } else {
46 auto match = [deviceAdapter](const QBluetoothHostInfo& info) {
47 return info.address() == deviceAdapter;
48 };
49
50 auto result = std::find_if(devices.begin(), devices.end(), match);
51 if (result == devices.end())
52 createAdapter = false;
53 }
54 }
55
56 /*
57 We assume that the current local adapter has been passed.
58 The logic below must change once there is more than one adapter.
59 */
60
61 if (createAdapter)
62 btAdapter = getDefaultBluetoothAdapter();
63
64 if (!btAdapter.isValid())
65 qCWarning(QT_BT_ANDROID) << "Platform does not support Bluetooth";
66
67 qRegisterMetaType<QList<QBluetoothUuid> >();
68}
69
71{
72 if (receiver) {
73 receiver->unregisterReceiver();
74 delete receiver;
75 }
76 if (localDeviceReceiver) {
77 localDeviceReceiver->unregisterReceiver();
78 delete localDeviceReceiver;
79 }
80}
81
82void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
83{
85
87 qCWarning(QT_BT_ANDROID) << "Service discovery start() failed due to missing permissions";
89 errorString = QBluetoothServiceDiscoveryAgent::tr(
90 "Failed to start service discovery due to missing permissions.");
91 emit q->errorOccurred(error);
93 return;
94 }
95
96 if (!btAdapter.isValid()) {
97 if (m_deviceAdapterAddress.isNull()) {
99 errorString = QBluetoothServiceDiscoveryAgent::tr("Platform does not support Bluetooth");
100 } else {
101 // specific adapter was requested which does not match the locally
102 // existing adapter
104 errorString = QBluetoothServiceDiscoveryAgent::tr("Invalid Bluetooth adapter address");
105 }
106
107 //abort any outstanding discoveries
109 emit q->errorOccurred(error);
111
112 return;
113 }
114
115 QJniObject inputString = QJniObject::fromString(address.toString());
116 QJniObject remoteDevice =
117 btAdapter.callMethod<QtJniTypes::BluetoothDevice>("getRemoteDevice",
118 inputString.object<jstring>());
119 if (!remoteDevice.isValid()) {
120
121 //if it was only device then its error -> otherwise go to next device
122 if (singleDevice) {
124 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot create Android BluetoothDevice");
125
126 qCWarning(QT_BT_ANDROID) << "Cannot start SDP for" << discoveredDevices.at(0).name()
127 << "(" << address.toString() << ")";
128 emit q->errorOccurred(error);
129 }
131 return;
132 }
133
134
136 qCDebug(QT_BT_ANDROID) << "Minimal discovery on (" << discoveredDevices.at(0).name()
137 << ")" << address.toString() ;
138
139 //Minimal discovery uses BluetoothDevice.getUuids()
140 QJniObject parcelUuidArray =
141 remoteDevice.callMethod<QtJniTypes::ParcelUuidArray>("getUuids");
142
143 if (!parcelUuidArray.isValid()) {
144 if (singleDevice) {
146 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot obtain service uuids");
147 emit q->errorOccurred(error);
148 }
149 qCWarning(QT_BT_ANDROID) << "Cannot retrieve SDP UUIDs for" << discoveredDevices.at(0).name()
150 << "(" << address.toString() << ")";
152 return;
153 }
154
155 const QList<QBluetoothUuid> results = ServiceDiscoveryBroadcastReceiver::convertParcelableArray(parcelUuidArray);
156 populateDiscoveredServices(discoveredDevices.at(0), results);
157
159 } else {
160 qCDebug(QT_BT_ANDROID) << "Full discovery on (" << discoveredDevices.at(0).name()
161 << ")" << address.toString();
162
163 //Full discovery uses BluetoothDevice.fetchUuidsWithSdp()
164 if (!receiver) {
165 receiver = new ServiceDiscoveryBroadcastReceiver();
167 q, [this](const QBluetoothAddress &address, const QList<QBluetoothUuid>& uuids) {
168 this->_q_processFetchedUuids(address, uuids);
169 });
170 }
171
172 if (!localDeviceReceiver) {
173 localDeviceReceiver = new LocalDeviceBroadcastReceiver();
175 q, [this](QBluetoothLocalDevice::HostMode state){
176 this->_q_hostModeStateChanged(state);
177 });
178 }
179
180 jboolean result = remoteDevice.callMethod<jboolean>("fetchUuidsWithSdp");
181 if (!result) {
182 //kill receiver to limit load of signals
183 receiver->unregisterReceiver();
184 receiver->deleteLater();
185 receiver = nullptr;
186 qCWarning(QT_BT_ANDROID) << "Cannot start dynamic fetch.";
188 }
189 }
190}
191
192void QBluetoothServiceDiscoveryAgentPrivate::stop()
193{
194 sdpCache.clear();
196
197 //kill receiver to limit load of signals
198 if (receiver) {
199 receiver->unregisterReceiver();
200 receiver->deleteLater();
201 receiver = nullptr;
202 }
203
205 emit q->canceled();
206
207}
208
209void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
210 const QBluetoothAddress &address, const QList<QBluetoothUuid> &uuids)
211{
212 //don't leave more data through if we are not interested anymore
214 return;
215
216 //could not find any service for the current address/device -> go to next one
217 if (address.isNull() || uuids.isEmpty()) {
218 if (discoveredDevices.size() == 1) {
221 this->_q_fetchUuidsTimeout();
222 }); // will also call _q_serviceDiscoveryFinished()
223 } else {
225 }
226 return;
227 }
228
229 if (QT_BT_ANDROID().isDebugEnabled()) {
230 qCDebug(QT_BT_ANDROID) << "Found UUID for" << address.toString()
231 << "\ncount: " << uuids.size();
232
234 for (const QBluetoothUuid &uuid : uuids)
235 result += uuid.toString() + QLatin1String("**");
236 qCDebug(QT_BT_ANDROID) << result;
237 }
238
239 /* In general there may be up-to two uuid events per device.
240 * We'll wait for the second event to arrive before we process the UUIDs.
241 * We utilize a timeout to catch cases when the second
242 * event doesn't arrive at all.
243 * Generally we assume that the second uuid event carries the most up-to-date
244 * set of uuids and discard the first events results.
245 */
246
247 if (sdpCache.contains(address)) {
248 //second event
249 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair = sdpCache.take(address);
250
251 //prefer second uuid set over first
252 populateDiscoveredServices(pair.first, uuids);
253
254 if (discoveredDevices.size() == 1 && sdpCache.isEmpty()) {
255 //last regular uuid data set from OS -> we finish here
257 }
258 } else {
259 //first event
260 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
261 pair.first = discoveredDevices.at(0);
262 pair.second = uuids;
263
264 if (pair.first.address() != address)
265 return;
266
267 sdpCache.insert(address, pair);
268
269 //the discovery on the last device cannot immediately finish
270 //we have to grant the timeout delay to allow potential second event to arrive
271 if (discoveredDevices.size() == 1) {
274 this->_q_fetchUuidsTimeout();
275 });
276 return;
277 }
278
280 }
281}
282
283void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QBluetoothDeviceInfo &remoteDevice, const QList<QBluetoothUuid> &uuids)
284{
285 /* Android doesn't provide decent SDP data. A flat list of uuids is all we get.
286 *
287 * The following approach is chosen:
288 * - If we see an SPP service class and we see
289 * one or more custom uuids we match them up. Such services will always
290 * be SPP services. There is the chance that a custom uuid is eronously
291 * mapped as being an SPP service. In addition, the SPP uuid will be mapped as
292 * standalone SPP service.
293 * - If we see a custom uuid but no SPP uuid then we return
294 * BluetoothServiceInfo instance with just a serviceUuid (no service class set)
295 * - If we don't find any custom uuid but the SPP uuid, we return a
296 * BluetoothServiceInfo instance where classId and serviceUuid() are set to SPP.
297 * - Any other service uuid will stand on its own.
298 * */
299
301
302 //find SPP and custom uuid
303 bool haveSppClass = false;
304 QVarLengthArray<qsizetype> customUuids;
305
306 for (qsizetype i = 0; i < uuids.size(); ++i) {
307 const QBluetoothUuid uuid = uuids.at(i);
308
309 if (uuid.isNull())
310 continue;
311
312 //check for SPP protocol
313 haveSppClass |= uuid == QBluetoothUuid::ServiceClassUuid::SerialPort;
314
315 //check for custom uuid
316 if (uuid.minimumSize() == 16)
317 customUuids.append(i);
318 }
319
320 auto rfcommProtocolDescriptorList = []() -> QBluetoothServiceInfo::Sequence {
324 return protocol;
325 };
326
327 auto sppProfileDescriptorList = []() -> QBluetoothServiceInfo::Sequence {
328 QBluetoothServiceInfo::Sequence profileSequence;
331 classId << QVariant::fromValue(quint16(0x100));
332 profileSequence.append(QVariant::fromValue(classId));
333 return profileSequence;
334 };
335
336 for (qsizetype i = 0; i < uuids.size(); ++i) {
337 const QBluetoothUuid &uuid = uuids.at(i);
338 if (uuid.isNull())
339 continue;
340
341 QBluetoothServiceInfo serviceInfo;
342 serviceInfo.setDevice(remoteDevice);
343
344 QBluetoothServiceInfo::Sequence protocolDescriptorList;
345 {
348 protocolDescriptorList.append(QVariant::fromValue(protocol));
349 }
350
351 if (customUuids.contains(i) && haveSppClass) {
352 //we have a custom uuid of service class type SPP
353
354 //set rfcomm protocol
355 protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList()));
356
357 //set SPP profile descriptor list
359 sppProfileDescriptorList());
360
362 //set SPP service class uuid
363 classId << QVariant::fromValue(uuid);
365 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
366
367 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile"));
368 serviceInfo.setServiceUuid(uuid);
370 //set rfcomm protocol
371 protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList()));
372
373 //set SPP profile descriptor list
375 sppProfileDescriptorList());
376
377 //also we need to set the custom uuid to the SPP uuid
378 //otherwise QBluetoothSocket::connectToService() would fail due to a missing service uuid
379 serviceInfo.setServiceUuid(uuid);
380 } else if (customUuids.contains(i)) {
381 //custom uuid but no serial port
382 serviceInfo.setServiceUuid(uuid);
383 }
384
385 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
388 serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
389
390 if (!customUuids.contains(i)) {
391 //if we don't have custom uuid use it as class id as well
393 classId << QVariant::fromValue(uuid);
394 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
395 auto clsId = QBluetoothUuid::ServiceClassUuid(uuid.toUInt16());
396 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
397 }
398
399 //Check if the service is in the uuidFilter
400 if (!uuidFilter.isEmpty()) {
401 bool match = uuidFilter.contains(serviceInfo.serviceUuid());
402 match |= uuidFilter.contains(QBluetoothSocketPrivateAndroid::reverseUuid(serviceInfo.serviceUuid()));
403 for (const auto &uuid : std::as_const(uuidFilter)) {
404 match |= serviceInfo.serviceClassUuids().contains(uuid);
405 match |= serviceInfo.serviceClassUuids().contains(QBluetoothSocketPrivateAndroid::reverseUuid(uuid));
406 }
407
408 if (!match)
409 continue;
410 }
411
412 //don't include the service if we already discovered it before
413 if (!isDuplicatedService(serviceInfo)) {
414 discoveredServices << serviceInfo;
415 // Use queued connection to allow us finish the service discovery reporting;
416 // the application might call stop() when it has detected the service-of-interest,
417 // which in turn can cause the use of already released resources
419 Q_ARG(QBluetoothServiceInfo, serviceInfo));
420 }
421 }
422}
423
424void QBluetoothServiceDiscoveryAgentPrivate::_q_fetchUuidsTimeout()
425{
426 // In practice if device list is empty, discovery has been stopped or bluetooth is offline
428 return;
429
430 // Process remaining services in the cache (these didn't get a second UUID event)
431 if (!sdpCache.isEmpty()) {
432 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
433 const QList<QBluetoothAddress> keys = sdpCache.keys();
434 for (const QBluetoothAddress &key : keys) {
435 pair = sdpCache.take(key);
436 populateDiscoveredServices(pair.first, pair.second);
437 }
438 }
439
440 Q_ASSERT(sdpCache.isEmpty());
441
442 //kill receiver to limit load of signals
443 if (receiver) {
444 receiver->unregisterReceiver();
445 receiver->deleteLater();
446 receiver = nullptr;
447 }
449}
450
451void QBluetoothServiceDiscoveryAgentPrivate::_q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state)
452{
455
457 sdpCache.clear();
459 errorString = QBluetoothServiceDiscoveryAgent::tr("Device is powered off");
460
461 //kill receiver to limit load of signals
462 if (receiver) {
463 receiver->unregisterReceiver();
464 receiver->deleteLater();
465 receiver = nullptr;
466 }
467
469 emit q->errorOccurred(error);
471 }
472}
473
QJniObject getDefaultBluetoothAdapter()
QT_BEGIN_NAMESPACE bool ensureAndroidPermission(QBluetoothPermission::CommunicationModes modes)
void hostModeStateChanged(QBluetoothLocalDevice::HostMode state)
\inmodule QtBluetooth
\inmodule QtBluetooth
QString name() const
Returns the name assigned to the device.
\inmodule QtBluetooth
HostMode
This enum describes the most of the local Bluetooth device.
static QList< QBluetoothHostInfo > allDevices()
Returns a list of all available local Bluetooth devices.
QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
\inmodule QtBluetooth
void setDevice(const QBluetoothDeviceInfo &info)
Sets the Bluetooth device that provides this service to device.
static QBluetoothUuid reverseUuid(const QBluetoothUuid &serviceUuid)
\inmodule QtBluetooth
quint16 toUInt16(bool *ok=nullptr) const
Returns the 16 bit representation of this UUID.
int minimumSize() const
Returns the minimum size in bytes that this UUID can be represented in.
ServiceClassUuid
This enum is a convienience type for Bluetooth service class and profile UUIDs.
static QString serviceClassToString(ServiceClassUuid uuid)
Returns a human-readable and translated name for the given service class represented by uuid.
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void clear()
Definition qlist.h:434
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
bool isNull() const noexcept
Returns true if this is the null UUID {00000000-0000-0000-0000-000000000000}; otherwise returns false...
Definition quuid.cpp:818
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
void uuidFetchFinished(const QBluetoothAddress &addr, const QList< QBluetoothUuid > &serviceUuid)
static QList< QBluetoothUuid > convertParcelableArray(const QJniObject &obj)
else opt state
[0]
Combined button and popup list for selecting options.
@ QueuedConnection
static QT_BEGIN_NAMESPACE constexpr auto uuidFetchTimeLimit
DBusConnection const char DBusError * error
EGLDeviceEXT * devices
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLenum mode
GLuint64 key
GLuint GLuint64EXT address
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
@ NoError
Definition main.cpp:34
#define emit
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
QStringList keys
QHostInfo info
[0]
char * toString(const MyType &t)
[31]
bool contains(const AT &t) const noexcept
Definition qlist.h:45
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...