diff options
author | Juha Vuolle <juha.vuolle@insta.fi> | 2022-06-13 21:17:15 +0300 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-06-21 16:23:56 +0000 |
commit | d7f42f2248d883978ae4641fbcb80d5512fcfb1c (patch) | |
tree | 247d9f65ffcebf35b12e52e60182e83243f00be2 | |
parent | 89937830c9d160f073c151558b279bc91815c028 (diff) | |
download | qtconnectivity-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>
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(); } |