summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@insta.fi>2022-06-13 21:17:15 +0300
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-06-21 16:23:56 +0000
commitd7f42f2248d883978ae4641fbcb80d5512fcfb1c (patch)
tree247d9f65ffcebf35b12e52e60182e83243f00be2
parent89937830c9d160f073c151558b279bc91815c028 (diff)
downloadqtconnectivity-d7f42f2248d883978ae4641fbcb80d5512fcfb1c.tar.gz
Add a timeout guard for Android BT device discovery not starting
In some bluetooth environments, after a full SDP service discovery, the device discovery can get stuck indefinitely. Everything seems to be starting fine but we do not get the DISCOVERY_STARTED action. In the normal case this action is received in < 1 second. This commit adds a timeout guard for this. If we don't get the DISCOVERY_STARTED action in time, we silently restart the query. Typically the discovery starts working after 10..20 seconds. Task-number: QTBUG-101066 Change-Id: Id6032ebeec97d1ad9eec07a946bc623c92500fd5 Reviewed-by: Ivan Solovev <ivan.solovev@qt.io> (cherry picked from commit eedaaca9634d56dce27601749049c81b201ab625) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp2
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver_p.h1
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp111
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.h4
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp2
5 files changed, 95 insertions, 25 deletions
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index caaabadf..fb4f7d45 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -413,7 +413,7 @@ void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, j
emit finished();
} else if (action == valueForStaticField(JavaNames::BluetoothAdapter,
JavaNames::ActionDiscoveryStarted).toString()) {
-
+ emit discoveryStarted();
} else if (action == valueForStaticField(JavaNames::BluetoothDevice,
JavaNames::ActionFound).toString()) {
//get BluetoothDevice
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
index 2e194a42..6645b517 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
@@ -70,6 +70,7 @@ public:
signals:
void deviceDiscovered(const QBluetoothDeviceInfo &info, bool isLeScanResult);
+ void discoveryStarted();
void finished();
private:
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
index a1a2ad58..2fda5be3 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -58,10 +58,14 @@ enum {
BtleScanActive = 2
};
+static constexpr auto deviceDiscoveryStartTimeLimit = std::chrono::seconds{5};
+static constexpr short deviceDiscoveryStartMaxAttempts = 6;
+
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) :
adapterAddress(deviceAdapter),
m_active(NoScanActive),
+ deviceDiscoveryStartAttemptsLeft(deviceDiscoveryStartMaxAttempts),
q_ptr(parent)
{
QJniEnvironment env;
@@ -101,9 +105,42 @@ QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent:
return (LowEnergyMethod | ClassicMethod);
}
+void QBluetoothDeviceDiscoveryAgentPrivate::classicDiscoveryStartFail()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Classic Discovery cannot be started");
+ emit q->errorOccurred(lastError);
+}
+
+// Sets & emits an error and returns true if bluetooth is off
+bool QBluetoothDeviceDiscoveryAgentPrivate::setErrorIfPowerOff()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ const int state = adapter.callMethod<jint>("getState");
+ if (state != 12) { // BluetoothAdapter.STATE_ON
+ lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
+ m_active = NoScanActive;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
+ emit q->errorOccurred(lastError);
+ return true;
+ }
+ return false;
+}
+
+/*
+The Classic/LE discovery method precedence is handled as follows:
+
+If only classic method is set => only classic method is used
+If only LE method is set => only LE method is used
+If both classic and LE methods are set, start classic scan first
+ If classic scan fails to start, start LE scan immediately in the start function
+ Otherwise start LE scan when classic scan completes
+*/
+
void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
{
- //TODO Implement discovery method handling (see input parameter)
requestedMethods = methods;
if (pendingCancel) {
@@ -131,13 +168,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
return;
}
- const int state = adapter.callMethod<jint>("getState");
- if (state != 12) { // BluetoothAdapter.STATE_ON
- lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
- errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
- emit q->errorOccurred(lastError);
+ if (setErrorIfPowerOff())
return;
- }
auto precisePermission = QtAndroidPrivate::PreciseLocation;
auto preciseCheckRes = QtAndroidPrivate::checkPermission(precisePermission).result();
@@ -216,16 +248,54 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
const bool success = adapter.callMethod<jboolean>("startDiscovery");
if (!success) {
qCDebug(QT_BT_ANDROID) << "Classic Discovery cannot be started";
+ // Check if only classic discovery requested -> error out and return.
+ // Otherwise since LE was also requested => don't return but allow the
+ // function to continue to LE scanning
if (requestedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) {
- //only classic discovery requested -> error out
- lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
- errorString = QBluetoothDeviceDiscoveryAgent::tr("Classic Discovery cannot be started");
-
- emit q->errorOccurred(lastError);
+ classicDiscoveryStartFail();
return;
- } // else fall through to LE discovery
+ }
} else {
m_active = SDPScanActive;
+ if (!deviceDiscoveryStartTimeout) {
+ // In some bluetooth environments device discovery does not start properly
+ // if it is done shortly after (up to 20 seconds) a full service discovery.
+ // In that case we never get DISOVERY_STARTED action and device discovery never
+ // finishes. Here we use a small timeout to guard it; if we don't get the
+ // 'started' action in time, we restart the query. In the normal case the action
+ // is received in < 1 second. See QTBUG-101066
+ deviceDiscoveryStartTimeout = new QTimer(this);
+ deviceDiscoveryStartTimeout->setInterval(deviceDiscoveryStartTimeLimit);
+ deviceDiscoveryStartTimeout->setSingleShot(true);
+ QObject::connect(receiver, &DeviceDiscoveryBroadcastReceiver::discoveryStarted,
+ deviceDiscoveryStartTimeout, &QTimer::stop);
+ QObject::connect(deviceDiscoveryStartTimeout, &QTimer::timeout, [this]() {
+ deviceDiscoveryStartAttemptsLeft -= 1;
+ qCWarning(QT_BT_ANDROID) << "Discovery start not received, attempts left:"
+ << deviceDiscoveryStartAttemptsLeft;
+ // Check that bluetooth is not switched off
+ if (setErrorIfPowerOff())
+ return;
+ // If this was the last retry attempt, cancel the discovery just in case
+ // as a good cleanup practice
+ if (deviceDiscoveryStartAttemptsLeft <= 0) {
+ qCWarning(QT_BT_ANDROID) << "Classic device discovery failed to start";
+ (void)adapter.callMethod<jboolean>("cancelDiscovery");
+ }
+ // Restart the discovery and retry timer.
+ // The logic below is similar as in the start()
+ if (deviceDiscoveryStartAttemptsLeft > 0 &&
+ adapter.callMethod<jboolean>("startDiscovery"))
+ deviceDiscoveryStartTimeout->start();
+ else if (requestedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod)
+ classicDiscoveryStartFail(); // No LE scan requested, scan is done
+ else
+ startLowEnergyScan(); // Continue to LE scan
+ });
+ }
+ deviceDiscoveryStartAttemptsLeft = deviceDiscoveryStartMaxAttempts;
+ deviceDiscoveryStartTimeout->start();
+
qCDebug(QT_BT_ANDROID)
<< "QBluetoothDeviceDiscoveryAgentPrivate::start() - Classic search successfully started.";
return;
@@ -234,7 +304,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
// LE search only requested or classic discovery failed but lets try LE scan anyway
- Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
startLowEnergyScan();
}
}
@@ -245,6 +314,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
pendingStart = false;
+ if (deviceDiscoveryStartTimeout)
+ deviceDiscoveryStartTimeout->stop();
+
if (m_active == NoScanActive)
return;
@@ -288,16 +360,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::processSdpDiscoveryFinished()
start(requestedMethods);
} else {
// check that it didn't finish due to turned off Bluetooth Device
- const int state = adapter.callMethod<jint>("getState");
- if (state != 12) { // BluetoothAdapter.STATE_ON
- m_active = NoScanActive;
- lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
- errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off");
- emit q->errorOccurred(lastError);
+ if (setErrorIfPowerOff())
return;
- }
-
- // no BTLE scan requested
+ // Since no BTLE scan requested and classic scan is done => finished()
if (!(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
m_active = NoScanActive;
emit q->finished();
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
index fa0d7768..27a05e65 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
@@ -150,12 +150,16 @@ private slots:
private:
void startLowEnergyScan();
+ void classicDiscoveryStartFail();
+ bool setErrorIfPowerOff();
DeviceDiscoveryBroadcastReceiver *receiver = nullptr;
short m_active;
QJniObject adapter;
QJniObject leScanner;
QTimer *leScanTimeout = nullptr;
+ QTimer *deviceDiscoveryStartTimeout = nullptr;
+ short deviceDiscoveryStartAttemptsLeft;
bool pendingCancel = false;
bool pendingStart = false;
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
index 5b9d1965..b3767f33 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
@@ -253,7 +253,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
Q_Q(QBluetoothServiceDiscoveryAgent);
QTimer::singleShot(4000, q, [this]() {
this->_q_fetchUuidsTimeout();
- }); // will also call _q_seriveDiscoveryFinished()
+ }); // will also call _q_serviceDiscoveryFinished()
} else {
_q_serviceDiscoveryFinished();
}