Warning: That file was not part of the compilation database. It may have many parsing errors.

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 <QtCore/qcoreapplication.h>
41#include <QtCore/QLoggingCategory>
42#include <QtCore/QTimer>
43#include <QtCore/private/qjnihelpers_p.h>
44#include <QtAndroidExtras/QAndroidJniEnvironment>
45#include <QtBluetooth/QBluetoothHostInfo>
46#include <QtBluetooth/QBluetoothLocalDevice>
47#include <QtBluetooth/QBluetoothServiceDiscoveryAgent>
48
49#include "qbluetoothservicediscoveryagent_p.h"
50#include "qbluetoothsocket_android_p.h"
51#include "android/servicediscoverybroadcastreceiver_p.h"
52#include "android/localdevicebroadcastreceiver_p.h"
53
54QT_BEGIN_NAMESPACE
55
56Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
57
58QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
59 QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
60 : error(QBluetoothServiceDiscoveryAgent::NoError),
61 m_deviceAdapterAddress(deviceAdapter),
62 state(Inactive),
63 mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
64 singleDevice(false),
65 q_ptr(qp)
66
67{
68 // If a specific adapter address is requested we need to check it matches
69 // the current local adapter. If it does not match we emit
70 // InvalidBluetoothAdapterError when calling start()
71
72 bool createAdapter = true;
73 if (!deviceAdapter.isNull()) {
74 const QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices();
75 if (devices.isEmpty()) {
76 createAdapter = false;
77 } else {
78 auto match = [deviceAdapter](const QBluetoothHostInfo& info) {
79 return info.address() == deviceAdapter;
80 };
81
82 auto result = std::find_if(devices.begin(), devices.end(), match);
83 if (result == devices.end())
84 createAdapter = false;
85 }
86 }
87
88 if (QtAndroidPrivate::androidSdkVersion() < 15)
89 qCWarning(QT_BT_ANDROID)
90 << "SDP not supported by Android API below version 15. Detected version: "
91 << QtAndroidPrivate::androidSdkVersion()
92 << "Service discovery will return empty list.";
93
94
95 /*
96 We assume that the current local adapter has been passed.
97 The logic below must change once there is more than one adapter.
98 */
99
100 if (createAdapter)
101 btAdapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
102 "getDefaultAdapter",
103 "()Landroid/bluetooth/BluetoothAdapter;");
104 if (!btAdapter.isValid())
105 qCWarning(QT_BT_ANDROID) << "Platform does not support Bluetooth";
106
107 qRegisterMetaType<QList<QBluetoothUuid> >();
108}
109
110QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
111{
112 if (receiver) {
113 receiver->unregisterReceiver();
114 delete receiver;
115 }
116 if (localDeviceReceiver) {
117 localDeviceReceiver->unregisterReceiver();
118 delete localDeviceReceiver;
119 }
120}
121
122void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
123{
124 Q_Q(QBluetoothServiceDiscoveryAgent);
125
126 if (!btAdapter.isValid()) {
127 if (m_deviceAdapterAddress.isNull()) {
128 error = QBluetoothServiceDiscoveryAgent::UnknownError;
129 errorString = QBluetoothServiceDiscoveryAgent::tr("Platform does not support Bluetooth");
130 } else {
131 // specific adapter was requested which does not match the locally
132 // existing adapter
133 error = QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError;
134 errorString = QBluetoothServiceDiscoveryAgent::tr("Invalid Bluetooth adapter address");
135 }
136
137 //abort any outstanding discoveries
138 discoveredDevices.clear();
139 emit q->error(error);
140 _q_serviceDiscoveryFinished();
141
142 return;
143 }
144
145 /* SDP discovery was officially added by Android API v15
146 * BluetoothDevice.getUuids() existed in earlier APIs already and in the future we may use
147 * reflection to support earlier Android versions than 15. Unfortunately
148 * BluetoothDevice.fetchUuidsWithSdp() and related APIs had some structure changes
149 * over time. Therefore we won't attempt this with reflection.
150 *
151 * TODO: Use reflection to support getUuuids() where possible.
152 * */
153 if (QtAndroidPrivate::androidSdkVersion() < 15) {
154 qCWarning(QT_BT_ANDROID) << "Aborting SDP enquiry due to too low Android API version (requires v15+)";
155
156 error = QBluetoothServiceDiscoveryAgent::UnknownError;
157 errorString = QBluetoothServiceDiscoveryAgent::tr("Android API below v15 does not support SDP discovery");
158
159 //abort any outstanding discoveries
160 sdpCache.clear();
161 discoveredDevices.clear();
162 emit q->error(error);
163 _q_serviceDiscoveryFinished();
164
165 return;
166 }
167
168 QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
169 QAndroidJniObject remoteDevice =
170 btAdapter.callObjectMethod("getRemoteDevice",
171 "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;",
172 inputString.object<jstring>());
173 QAndroidJniEnvironment env;
174 if (env->ExceptionCheck()) {
175 env->ExceptionClear();
176 env->ExceptionDescribe();
177
178 //if it was only device then its error -> otherwise go to next device
179 if (singleDevice) {
180 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
181 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot create Android BluetoothDevice");
182
183 qCWarning(QT_BT_ANDROID) << "Cannot start SDP for" << discoveredDevices.at(0).name()
184 << "(" << address.toString() << ")";
185 emit q->error(error);
186 }
187 _q_serviceDiscoveryFinished();
188 return;
189 }
190
191
192 if (mode == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
193 qCDebug(QT_BT_ANDROID) << "Minimal discovery on (" << discoveredDevices.at(0).name()
194 << ")" << address.toString() ;
195
196 //Minimal discovery uses BluetoothDevice.getUuids()
197 QAndroidJniObject parcelUuidArray = remoteDevice.callObjectMethod(
198 "getUuids", "()[Landroid/os/ParcelUuid;");
199
200 if (!parcelUuidArray.isValid()) {
201 if (singleDevice) {
202 error = QBluetoothServiceDiscoveryAgent::InputOutputError;
203 errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot obtain service uuids");
204 emit q->error(error);
205 }
206 qCWarning(QT_BT_ANDROID) << "Cannot retrieve SDP UUIDs for" << discoveredDevices.at(0).name()
207 << "(" << address.toString() << ")";
208 _q_serviceDiscoveryFinished();
209 return;
210 }
211
212 const QList<QBluetoothUuid> results = ServiceDiscoveryBroadcastReceiver::convertParcelableArray(parcelUuidArray);
213 populateDiscoveredServices(discoveredDevices.at(0), results);
214
215 _q_serviceDiscoveryFinished();
216 } else {
217 qCDebug(QT_BT_ANDROID) << "Full discovery on (" << discoveredDevices.at(0).name()
218 << ")" << address.toString();
219
220 //Full discovery uses BluetoothDevice.fetchUuidsWithSdp()
221 if (!receiver) {
222 receiver = new ServiceDiscoveryBroadcastReceiver();
223 QObject::connect(receiver, &ServiceDiscoveryBroadcastReceiver::uuidFetchFinished,
224 q, [this](const QBluetoothAddress &address, const QList<QBluetoothUuid>& uuids) {
225 this->_q_processFetchedUuids(address, uuids);
226 });
227 }
228
229 if (!localDeviceReceiver) {
230 localDeviceReceiver = new LocalDeviceBroadcastReceiver();
231 QObject::connect(localDeviceReceiver, &LocalDeviceBroadcastReceiver::hostModeStateChanged,
232 q, [this](QBluetoothLocalDevice::HostMode state){
233 this->_q_hostModeStateChanged(state);
234 });
235 }
236
237 jboolean result = remoteDevice.callMethod<jboolean>("fetchUuidsWithSdp");
238 if (!result) {
239 //kill receiver to limit load of signals
240 receiver->unregisterReceiver();
241 receiver->deleteLater();
242 receiver = nullptr;
243 qCWarning(QT_BT_ANDROID) << "Cannot start dynamic fetch.";
244 _q_serviceDiscoveryFinished();
245 }
246 }
247}
248
249void QBluetoothServiceDiscoveryAgentPrivate::stop()
250{
251 sdpCache.clear();
252 discoveredDevices.clear();
253
254 //kill receiver to limit load of signals
255 receiver->unregisterReceiver();
256 receiver->deleteLater();
257 receiver = nullptr;
258
259 Q_Q(QBluetoothServiceDiscoveryAgent);
260 emit q->canceled();
261
262}
263
264void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
265 const QBluetoothAddress &address, const QList<QBluetoothUuid> &uuids)
266{
267 //don't leave more data through if we are not interested anymore
268 if (discoveredDevices.count() == 0)
269 return;
270
271 //could not find any service for the current address/device -> go to next one
272 if (address.isNull() || uuids.isEmpty()) {
273 if (discoveredDevices.count() == 1) {
274 Q_Q(QBluetoothServiceDiscoveryAgent);
275 QTimer::singleShot(4000, q, [this]() {
276 this->_q_fetchUuidsTimeout();
277 });
278 }
279 _q_serviceDiscoveryFinished();
280 return;
281 }
282
283 if (QT_BT_ANDROID().isDebugEnabled()) {
284 qCDebug(QT_BT_ANDROID) << "Found UUID for" << address.toString()
285 << "\ncount: " << uuids.count();
286
287 QString result;
288 for (int i = 0; i<uuids.count(); i++)
289 result += uuids.at(i).toString() + QStringLiteral("**");
290 qCDebug(QT_BT_ANDROID) << result;
291 }
292
293 /* In general there are two uuid events per device.
294 * We'll wait for the second event to arrive before we process the UUIDs.
295 * We utilize a timeout to catch cases when the second
296 * event doesn't arrive at all.
297 * Generally we assume that the second uuid event carries the most up-to-date
298 * set of uuids and discard the first events results.
299 */
300
301 if (sdpCache.contains(address)) {
302 //second event
303 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair = sdpCache.take(address);
304
305 //prefer second uuid set over first
306 populateDiscoveredServices(pair.first, uuids);
307
308 if (discoveredDevices.count() == 1 && sdpCache.isEmpty()) {
309 //last regular uuid data set from OS -> we finish here
310 _q_serviceDiscoveryFinished();
311 }
312 } else {
313 //first event
314 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
315 pair.first = discoveredDevices.at(0);
316 pair.second = uuids;
317
318 if (pair.first.address() != address)
319 return;
320
321 sdpCache.insert(address, pair);
322
323 //the discovery on the last device cannot immediately finish
324 //we have to grant the 2 seconds timeout delay
325 if (discoveredDevices.count() == 1) {
326 Q_Q(QBluetoothServiceDiscoveryAgent);
327 QTimer::singleShot(4000, q, [this]() {
328 this->_q_fetchUuidsTimeout();
329 });
330 return;
331 }
332
333 _q_serviceDiscoveryFinished();
334 }
335}
336
337void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QBluetoothDeviceInfo &remoteDevice, const QList<QBluetoothUuid> &uuids)
338{
339 /* Android doesn't provide decent SDP data. A flat list of uuids is all we get.
340 *
341 * The following approach is chosen:
342 * - If we see an SPP service class and we see
343 * one or more custom uuids we match them up. Such services will always
344 * be SPP services. There is the chance that a custom uuid is eronously
345 * mapped as being an SPP service. In addition, the SPP uuid will be mapped as
346 * standalone SPP service.
347 * - If we see a custom uuid but no SPP uuid then we return
348 * BluetoothServiceInfo instance with just a serviceUuid (no service class set)
349 * - If we don't find any custom uuid but the SPP uuid, we return a
350 * BluetoothServiceInfo instance where classId and serviceUuid() are set to SPP.
351 * - Any other service uuid will stand on its own.
352 * */
353
354 Q_Q(QBluetoothServiceDiscoveryAgent);
355
356 //find SPP and custom uuid
357 bool haveSppClass = false;
358 QVector<int> customUuids;
359
360 for (int i = 0; i < uuids.count(); i++) {
361 const QBluetoothUuid uuid = uuids.at(i);
362
363 if (uuid.isNull())
364 continue;
365
366 //check for SPP protocol
367 bool ok = false;
368 auto uuid16 = uuid.toUInt16(&ok);
369 haveSppClass |= ok && uuid16 == QBluetoothUuid::SerialPort;
370
371 //check for custom uuid
372 if (uuid.minimumSize() == 16)
373 customUuids.append(i);
374 }
375
376 auto rfcommProtocolDescriptorList = []() -> QBluetoothServiceInfo::Sequence {
377 QBluetoothServiceInfo::Sequence protocol;
378 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
379 << QVariant::fromValue(0);
380 return protocol;
381 };
382
383 auto sppProfileDescriptorList = []() -> QBluetoothServiceInfo::Sequence {
384 QBluetoothServiceInfo::Sequence profileSequence;
385 QBluetoothServiceInfo::Sequence classId;
386 classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
387 classId << QVariant::fromValue(quint16(0x100));
388 profileSequence.append(QVariant::fromValue(classId));
389 return profileSequence;
390 };
391
392 for (int i = 0; i < uuids.count(); i++) {
393 const QBluetoothUuid &uuid = uuids.at(i);
394 if (uuid.isNull())
395 continue;
396
397 QBluetoothServiceInfo serviceInfo;
398 serviceInfo.setDevice(remoteDevice);
399
400 QBluetoothServiceInfo::Sequence protocolDescriptorList;
401 {
402 QBluetoothServiceInfo::Sequence protocol;
403 protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
404 protocolDescriptorList.append(QVariant::fromValue(protocol));
405 }
406
407 if (customUuids.contains(i) && haveSppClass) {
408 //we have a custom uuid of service class type SPP
409
410 //set rfcomm protocol
411 protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList()));
412
413 //set SPP profile descriptor list
414 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
415 sppProfileDescriptorList());
416
417 QBluetoothServiceInfo::Sequence classId;
418 //set SPP service class uuid
419 classId << QVariant::fromValue(uuid);
420 classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
421 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
422
423 serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile"));
424 serviceInfo.setServiceUuid(uuid);
425 } else if (uuid == QBluetoothUuid{QBluetoothUuid::SerialPort}) {
426 //set rfcomm protocol
427 protocolDescriptorList.append(QVariant::fromValue(rfcommProtocolDescriptorList()));
428
429 //set SPP profile descriptor list
430 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
431 sppProfileDescriptorList());
432
433 //also we need to set the custom uuid to the SPP uuid
434 //otherwise QBluetoothSocket::connectToService() would fail due to a missing service uuid
435 serviceInfo.setServiceUuid(uuid);
436 } else if (customUuids.contains(i)) {
437 //custom uuid but no serial port
438 serviceInfo.setServiceUuid(uuid);
439 }
440
441 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
442 QBluetoothServiceInfo::Sequence publicBrowse;
443 publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
444 serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
445
446 if (!customUuids.contains(i)) {
447 //if we don't have custom uuid use it as class id as well
448 QBluetoothServiceInfo::Sequence classId;
449 classId << QVariant::fromValue(uuid);
450 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
451 auto clsId = QBluetoothUuid::ServiceClassUuid(uuid.toUInt16());
452 serviceInfo.setServiceName(QBluetoothUuid::serviceClassToString(clsId));
453 }
454
455 //Check if the service is in the uuidFilter
456 if (!uuidFilter.isEmpty()) {
457 bool match = uuidFilter.contains(serviceInfo.serviceUuid());
458 match |= uuidFilter.contains(QBluetoothSocketPrivateAndroid::reverseUuid(serviceInfo.serviceUuid()));
459 for (const auto &uuid : qAsConst(uuidFilter)) {
460 match |= serviceInfo.serviceClassUuids().contains(uuid);
461 match |= serviceInfo.serviceClassUuids().contains(QBluetoothSocketPrivateAndroid::reverseUuid(uuid));
462 }
463
464 if (!match)
465 continue;
466 }
467
468 //don't include the service if we already discovered it before
469 if (!isDuplicatedService(serviceInfo)) {
470 discoveredServices << serviceInfo;
471 //qCDebug(QT_BT_ANDROID) << serviceInfo;
472 emit q->serviceDiscovered(serviceInfo);
473 }
474 }
475}
476
477void QBluetoothServiceDiscoveryAgentPrivate::_q_fetchUuidsTimeout()
478{
479 if (sdpCache.isEmpty())
480 return;
481
482 QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
483 const QList<QBluetoothAddress> keys = sdpCache.keys();
484 for (const QBluetoothAddress &key : keys) {
485 pair = sdpCache.take(key);
486 populateDiscoveredServices(pair.first, pair.second);
487 }
488
489 Q_ASSERT(sdpCache.isEmpty());
490
491 //kill receiver to limit load of signals
492 receiver->unregisterReceiver();
493 receiver->deleteLater();
494 receiver = nullptr;
495 _q_serviceDiscoveryFinished();
496}
497
498void QBluetoothServiceDiscoveryAgentPrivate::_q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state)
499{
500 if (discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery &&
501 state == QBluetoothLocalDevice::HostPoweredOff ) {
502
503 discoveredDevices.clear();
504 sdpCache.clear();
505 error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
506 errorString = QBluetoothServiceDiscoveryAgent::tr("Device is powered off");
507
508 //kill receiver to limit load of signals
509 receiver->unregisterReceiver();
510 receiver->deleteLater();
511 receiver = nullptr;
512
513 Q_Q(QBluetoothServiceDiscoveryAgent);
514 emit q->error(error);
515 _q_serviceDiscoveryFinished();
516 }
517}
518
519QT_END_NAMESPACE
520

Warning: That file was not part of the compilation database. It may have many parsing errors.