5#include "android/devicediscoverybroadcastreceiver_p.h"
6#include "android/androidutils_p.h"
7#include "android/jni_android_p.h"
9#include <QCoreApplication>
10#include <QtCore/QLoggingCategory>
11#include <QtCore/qpermissions.h>
12#include <QtBluetooth/QBluetoothAddress>
13#include <QtBluetooth/QBluetoothDeviceInfo>
14#include <QtCore/QJniEnvironment>
15#include <QtCore/private/qandroidextras_p.h>
19Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
30QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
31 const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) :
32 adapterAddress(deviceAdapter),
33 m_active(NoScanActive),
34 deviceDiscoveryStartAttemptsLeft(deviceDiscoveryStartMaxAttempts),
37 adapter = getDefaultBluetoothAdapter();
39 if (!adapter.isValid())
40 qCWarning(QT_BT_ANDROID) <<
"Device does not support Bluetooth";
43QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
45 if (m_active != NoScanActive)
48 if (leScanner.isValid())
49 leScanner.setField(
"qtObject",
reinterpret_cast<jlong>(
nullptr));
52 receiver->unregisterReceiver();
57bool QBluetoothDeviceDiscoveryAgentPrivate::isActive()
const
63 return m_active != NoScanActive;
66QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods()
68 return (LowEnergyMethod | ClassicMethod);
71void QBluetoothDeviceDiscoveryAgentPrivate::classicDiscoveryStartFail()
73 Q_Q(QBluetoothDeviceDiscoveryAgent);
74 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
75 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Classic Discovery cannot be started");
76 emit q->errorOccurred(lastError);
80bool QBluetoothDeviceDiscoveryAgentPrivate::setErrorIfPowerOff()
82 Q_Q(QBluetoothDeviceDiscoveryAgent);
84 const int state = adapter.callMethod<jint>(
"getState");
86 lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
87 m_active = NoScanActive;
88 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Device is powered off");
89 emit q->errorOccurred(lastError);
96
97
98
99
100
101
102
103
105void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
107 requestedMethods = methods;
114 Q_Q(QBluetoothDeviceDiscoveryAgent);
116 if (!adapter.isValid()) {
117 qCWarning(QT_BT_ANDROID) <<
"Device does not support Bluetooth";
118 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
119 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Device does not support Bluetooth");
120 emit q->errorOccurred(lastError);
124 if (!adapterAddress.isNull()
125 && adapter.callMethod<jstring>(
"getAddress").toString()
126 != adapterAddress.toString()) {
127 qCWarning(QT_BT_ANDROID) <<
"Incorrect local adapter passed.";
128 lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError;
129 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Passed address is not a local device.");
130 emit q->errorOccurred(lastError);
134 if (setErrorIfPowerOff())
137 if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
138 qCWarning(QT_BT_ANDROID)
139 <<
"Search not possible due to missing QBluetoothPermission::Access permission";
140 errorString = QBluetoothDeviceDiscoveryAgent::tr(
141 "Failed to start device discovery due to missing permissions.");
142 lastError = QBluetoothDeviceDiscoveryAgent::MissingPermissionsError;
143 emit q->errorOccurred(lastError);
146 qCDebug(QT_BT_ANDROID) <<
"QBluetoothPermission::Access permission available";
148 const bool scanNeedsLocation = (
bool)QtJniTypes::QtBtUtility::callStaticMethod<jboolean>(
149 "bluetoothScanRequiresLocation", QNativeInterface::QAndroidApplication::context());
151 qCDebug(QT_BT_ANDROID) <<
"Is location service and location permission required for scan:"
152 << scanNeedsLocation;
154 if (scanNeedsLocation) {
158 QLocationPermission locationPermission;
159 locationPermission.setAccuracy(QLocationPermission::Accuracy::Precise);
161 if (qApp->checkPermission(locationPermission) != Qt::PermissionStatus::Granted) {
162 qCWarning(QT_BT_ANDROID) <<
"Search not possible due to missing location permissions";
163 lastError = QBluetoothDeviceDiscoveryAgent::LocationServiceTurnedOffError;
164 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Location permission not granted. Search is not possible.");
165 emit q->errorOccurred(lastError);
169 qCDebug(QT_BT_ANDROID) <<
"Location permission granted";
172 bool locationTurnedOn =
true;
173 const QJniObject locString = QJniObject::getStaticObjectField(
174 "android/content/Context",
"LOCATION_SERVICE",
"Ljava/lang/String;");
176 const QJniObject locService =
177 QJniObject(QNativeInterface::QAndroidApplication::context()).callMethod<jobject>(
179 locString.object<jstring>());
181 if (locService.isValid()) {
182 if (QNativeInterface::QAndroidApplication::sdkVersion() >= 28) {
183 locationTurnedOn =
bool(locService.callMethod<jboolean>(
"isLocationEnabled"));
186 QJniObject listOfEnabledProviders =
187 locService.callMethod<QtJniTypes::List>(
"getProviders",
true);
189 if (listOfEnabledProviders.isValid()) {
190 int size = listOfEnabledProviders.callMethod<jint>(
"size");
191 locationTurnedOn = size > 0;
192 qCDebug(QT_BT_ANDROID) << size <<
"enabled location providers detected.";
197 if (!locationTurnedOn) {
198 qCWarning(QT_BT_ANDROID) <<
"Search not possible due to turned off Location service";
199 lastError = QBluetoothDeviceDiscoveryAgent::LocationServiceTurnedOffError;
200 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Location service turned off. Search is not possible.");
201 emit q->errorOccurred(lastError);
205 qCDebug(QT_BT_ANDROID) <<
"Location turned on";
211 receiver =
new DeviceDiscoveryBroadcastReceiver();
212 qRegisterMetaType<QBluetoothDeviceInfo>();
213 QObject::connect(receiver, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo,
bool)),
214 this, SLOT(processDiscoveredDevices(QBluetoothDeviceInfo,
bool)));
215 QObject::connect(receiver, SIGNAL(finished()),
this, SLOT(processSdpDiscoveryFinished()));
218 lastError = QBluetoothDeviceDiscoveryAgent::NoError;
220 discoveredDevices.clear();
223 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
224 const bool success = adapter.callMethod<jboolean>(
"startDiscovery");
226 qCDebug(QT_BT_ANDROID) <<
"Classic Discovery cannot be started";
230 if (requestedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
231 classicDiscoveryStartFail();
235 m_active = SDPScanActive;
236 if (!deviceDiscoveryStartTimeout) {
243 deviceDiscoveryStartTimeout =
new QTimer(
this);
244 deviceDiscoveryStartTimeout->setInterval(deviceDiscoveryStartTimeLimit);
245 deviceDiscoveryStartTimeout->setSingleShot(
true);
246 QObject::connect(receiver, &DeviceDiscoveryBroadcastReceiver::discoveryStarted,
247 deviceDiscoveryStartTimeout, &QTimer::stop);
248 QObject::connect(deviceDiscoveryStartTimeout, &QTimer::timeout,
this, [
this]() {
249 deviceDiscoveryStartAttemptsLeft -= 1;
250 qCWarning(QT_BT_ANDROID) <<
"Discovery start not received, attempts left:"
251 << deviceDiscoveryStartAttemptsLeft;
253 if (setErrorIfPowerOff())
257 if (deviceDiscoveryStartAttemptsLeft <= 0) {
258 qCWarning(QT_BT_ANDROID) <<
"Classic device discovery failed to start";
259 (
void)adapter.callMethod<jboolean>(
"cancelDiscovery");
263 if (deviceDiscoveryStartAttemptsLeft > 0 &&
264 adapter.callMethod<jboolean>(
"startDiscovery"))
265 deviceDiscoveryStartTimeout->start();
266 else if (requestedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod)
267 classicDiscoveryStartFail();
269 startLowEnergyScan();
272 deviceDiscoveryStartAttemptsLeft = deviceDiscoveryStartMaxAttempts;
273 deviceDiscoveryStartTimeout->start();
275 qCDebug(QT_BT_ANDROID)
276 <<
"QBluetoothDeviceDiscoveryAgentPrivate::start() - Classic search successfully started.";
281 if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
283 startLowEnergyScan();
287void QBluetoothDeviceDiscoveryAgentPrivate::stop()
289 Q_Q(QBluetoothDeviceDiscoveryAgent);
291 pendingStart =
false;
293 if (deviceDiscoveryStartTimeout)
294 deviceDiscoveryStartTimeout->stop();
296 if (m_active == NoScanActive)
299 if (m_active == SDPScanActive) {
307 pendingCancel =
true;
308 bool success = adapter.callMethod<jboolean>(
"cancelDiscovery");
310 lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
311 errorString = QBluetoothDeviceDiscoveryAgent::tr(
"Discovery cannot be stopped");
312 emit q->errorOccurred(lastError);
315 }
else if (m_active == BtleScanActive) {
318 pendingCancel =
true;
319 if (leScanTimeout->isActive())
320 leScanTimeout->stop();
325void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
330 if (m_active != SDPScanActive)
333 Q_Q(QBluetoothDeviceDiscoveryAgent);
335 if (pendingCancel && !pendingStart) {
336 m_active = NoScanActive;
337 pendingCancel =
false;
339 }
else if (pendingStart) {
340 pendingStart = pendingCancel =
false;
341 start(requestedMethods);
344 if (setErrorIfPowerOff())
347 if (!(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
348 m_active = NoScanActive;
353 startLowEnergyScan();
357void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevices(
358 const QBluetoothDeviceInfo &info,
bool isLeResult)
362 if (m_active != SDPScanActive && !isLeResult)
364 if (m_active != BtleScanActive && isLeResult)
367 Q_Q(QBluetoothDeviceDiscoveryAgent);
374 for (qsizetype i = 0; i < discoveredDevices.size(); ++i) {
375 if (discoveredDevices[i].address() == info.address()) {
376 QBluetoothDeviceInfo::Fields updatedFields = QBluetoothDeviceInfo::Field::None;
377 if (discoveredDevices[i].rssi() != info.rssi()) {
378 qCDebug(QT_BT_ANDROID) <<
"Updating RSSI for" << info.address()
380 discoveredDevices[i].setRssi(info.rssi());
381 updatedFields.setFlag(QBluetoothDeviceInfo::Field::RSSI);
383 if (discoveredDevices[i].manufacturerData() != info.manufacturerData()) {
384 qCDebug(QT_BT_ANDROID) <<
"Updating manufacturer data for" << info.address();
385 const QList<quint16> keys = info.manufacturerIds();
387 discoveredDevices[i].setManufacturerData(key, info.manufacturerData(key));
388 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ManufacturerData);
390 if (discoveredDevices[i].serviceData() != info.serviceData()) {
391 qCDebug(QT_BT_ANDROID) <<
"Updating service data for" << info.address();
392 const QList<QBluetoothUuid> keys = info.serviceIds();
393 for (
auto key : keys)
394 discoveredDevices[i].setServiceData(key, info.serviceData(key));
395 updatedFields.setFlag(QBluetoothDeviceInfo::Field::ServiceData);
398 if (lowEnergySearchTimeout > 0) {
399 if (discoveredDevices[i] != info) {
400 if (discoveredDevices.at(i).name() == info.name()) {
401 qCDebug(QT_BT_ANDROID) <<
"Almost Duplicate " << info.address()
402 << info.name() <<
"- replacing in place";
403 discoveredDevices.replace(i, info);
404 emit q->deviceDiscovered(info);
407 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
408 emit q->deviceUpdated(discoveredDevices[i], updatedFields);
414 discoveredDevices.replace(i, info);
415 emit q->deviceDiscovered(info);
417 if (!updatedFields.testFlag(QBluetoothDeviceInfo::Field::None))
418 emit q->deviceUpdated(discoveredDevices[i], updatedFields);
424 discoveredDevices.append(info);
425 qCDebug(QT_BT_ANDROID) <<
"Device found: " << info.name() << info.address().toString()
426 <<
"isLeScanResult:" << isLeResult
427 <<
"Manufacturer data size:" << info.manufacturerData().size();
428 emit q->deviceDiscovered(info);
431void QBluetoothDeviceDiscoveryAgentPrivate::startLowEnergyScan()
433 Q_Q(QBluetoothDeviceDiscoveryAgent);
435 m_active = BtleScanActive;
438 if (!leScanner.isValid()) {
439 leScanner = QJniObject::construct<QtJniTypes::QtBtLECentral>(
440 QNativeInterface::QAndroidApplication::context());
441 if (!leScanner.isValid()) {
442 qCWarning(QT_BT_ANDROID) <<
"Cannot load BTLE device scan class";
443 m_active = NoScanActive;
448 leScanner.setField(
"qtObject",
reinterpret_cast<jlong>(receiver));
451 jboolean result = leScanner.callMethod<jboolean>(
"scanForLeDevice",
true);
453 qCWarning(QT_BT_ANDROID) <<
"Cannot start BTLE device scanner";
454 m_active = NoScanActive;
460 if (!leScanTimeout) {
461 leScanTimeout =
new QTimer(
this);
462 leScanTimeout->setSingleShot(
true);
463 connect(leScanTimeout, &QTimer::timeout,
464 this, &QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan);
467 if (lowEnergySearchTimeout > 0) {
468 leScanTimeout->setInterval(lowEnergySearchTimeout);
469 leScanTimeout->start();
472 qCDebug(QT_BT_ANDROID)
473 <<
"QBluetoothDeviceDiscoveryAgentPrivate::start() - Low Energy search successfully started.";
476void QBluetoothDeviceDiscoveryAgentPrivate::processLeDiscoveryFinished(
int epoch)
479 if (epoch != leScanEpoch || m_active != BtleScanActive)
482 m_active = NoScanActive;
485 Q_Q(QBluetoothDeviceDiscoveryAgent);
486 if (pendingCancel && !pendingStart) {
487 pendingCancel =
false;
489 }
else if (pendingStart) {
490 pendingStart = pendingCancel =
false;
491 start(requestedMethods);
498void QBluetoothDeviceDiscoveryAgentPrivate::stopLowEnergyScan()
500 jboolean result = leScanner.callMethod<jboolean>(
"scanForLeDevice",
false);
502 qCWarning(QT_BT_ANDROID) <<
"Cannot stop BTLE device scanner";
506 QMetaObject::invokeMethod(
this,
507 [
this, epoch = leScanEpoch]() { processLeDiscoveryFinished(epoch); },
508 Qt::QueuedConnection);
Combined button and popup list for selecting options.
static constexpr auto deviceDiscoveryStartTimeLimit
static constexpr short deviceDiscoveryStartMaxAttempts