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_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
6#include "android/servicediscoverybroadcastreceiver_p.h"
7#include "android/localdevicebroadcastreceiver_p.h"
8#include "android/androidutils_p.h"
9#include "android/jni_android_p.h"
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)
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{
84 Q_Q(QBluetoothServiceDiscoveryAgent);
85
86 if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
87 qCWarning(QT_BT_ANDROID) << "Service discovery start() failed due to missing permissions";
88 error = QBluetoothServiceDiscoveryAgent::MissingPermissionsError;
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()) {
98 error = QBluetoothServiceDiscoveryAgent::UnknownError;
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
103 error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
104 errorString = QBluetoothServiceDiscoveryAgent::tr("Invalid Bluetooth adapter address");
105 }
106
107 //abort any outstanding discoveries
108 discoveredDevices.clear();
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) {
123 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
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
135 if (mode == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
136 qCDebug(QT_BT_ANDROID) << "Minimal discovery on (" << discoveredDevices.at(0).name()
137 << ")" << address.toString() ;
138
139 //Minimal discovery uses BluetoothDevice.getUuids()
140 const QJniArray parcelUuidArray =
141 remoteDevice.callMethod<QtJniTypes::ParcelUuid[]>("getUuids");
142
143 if (!parcelUuidArray.isValid()) {
144 if (singleDevice) {
145 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
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();
166 QObject::connect(receiver, &ServiceDiscoveryBroadcastReceiver::uuidFetchFinished,
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();
174 QObject::connect(localDeviceReceiver, &LocalDeviceBroadcastReceiver::hostModeStateChanged,
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
193{
194 sdpCache.clear();
195 discoveredDevices.clear();
196
197 //kill receiver to limit load of signals
198 if (receiver) {
199 receiver->unregisterReceiver();
200 receiver->deleteLater();
201 receiver = nullptr;
202 }
203
204 Q_Q(QBluetoothServiceDiscoveryAgent);
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
213 if (discoveredDevices.isEmpty())
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) {
219 Q_Q(QBluetoothServiceDiscoveryAgent);
220 QTimer::singleShot(uuidFetchTimeLimit, q, [this]() {
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
233 QString result;
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) {
272 Q_Q(QBluetoothServiceDiscoveryAgent);
273 QTimer::singleShot(uuidFetchTimeLimit, q, [this]() {
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
300 Q_Q(QBluetoothServiceDiscoveryAgent);
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 {
321 QBluetoothServiceInfo::Sequence protocol;
322 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Rfcomm))
323 << QVariant::fromValue(0);
324 return protocol;
325 };
326
327 auto sppProfileDescriptorList = []() -> QBluetoothServiceInfo::Sequence {
328 QBluetoothServiceInfo::Sequence profileSequence;
329 QBluetoothServiceInfo::Sequence classId;
330 classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::SerialPort));
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 {
346 QBluetoothServiceInfo::Sequence protocol;
347 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap));
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
358 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
359 sppProfileDescriptorList());
360
361 QBluetoothServiceInfo::Sequence classId;
362 //set SPP service class uuid
363 classId << QVariant::fromValue(uuid);
364 classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::SerialPort));
365 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
366
367 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile"));
368 serviceInfo.setServiceUuid(uuid);
369 } else if (uuid == QBluetoothUuid{QBluetoothUuid::ServiceClassUuid::SerialPort}) {
370 //set rfcomm protocol
371 protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList()));
372
373 //set SPP profile descriptor list
374 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
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);
386 QBluetoothServiceInfo::Sequence publicBrowse;
387 publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup));
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
392 QBluetoothServiceInfo::Sequence classId;
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
418 QMetaObject::invokeMethod(q, "serviceDiscovered", Qt::QueuedConnection,
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
427 if (discoveredDevices.isEmpty())
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{
453 if (discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery &&
454 state == QBluetoothLocalDevice::HostPoweredOff ) {
455
456 discoveredDevices.clear();
457 sdpCache.clear();
458 error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
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
468 Q_Q(QBluetoothServiceDiscoveryAgent);
469 emit q->errorOccurred(error);
470 _q_serviceDiscoveryFinished();
471 }
472}
473
474QT_END_NAMESPACE
QBluetoothServiceDiscoveryAgentPrivate(QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
\inmodule QtBluetooth