summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarja Sundqvist <tarja.sundqvist@qt.io>2023-06-09 17:08:44 +0300
committerTarja Sundqvist <tarja.sundqvist@qt.io>2023-06-09 17:08:44 +0300
commitf0c98c39447894bba493aa91c65053d7f18d68cb (patch)
treeb895c2a764b5c77edd75ae7af5d4336191a8301c
parente2dd32305ec52c96518a4357423d546c7c66f9a6 (diff)
parent807c62ddee0749b68458654f7d37b28aceca6359 (diff)
Merge remote-tracking branch 'origin/tqtc/lts-5.15.11' into tqtc/lts-5.15-opensourcev5.15.11-lts-lgpl
-rw-r--r--.qmake.conf2
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp2
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver_p.h1
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp124
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.h4
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp31
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_p.h6
-rw-r--r--src/bluetooth/qbluetoothsocket.cpp3
-rw-r--r--src/bluetooth/qbluetoothsocket_bluez.cpp6
-rw-r--r--tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp3
10 files changed, 137 insertions, 45 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 5e6fec74..259b036a 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -2,4 +2,4 @@ load(qt_build_config)
DEFINES += QT_NO_FOREACH QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST
-MODULE_VERSION = 5.15.10
+MODULE_VERSION = 5.15.11
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index f1f50516..92d519ea 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -421,7 +421,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 0302971d..24013e74 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 228c8bb3..53bd856b 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -58,6 +58,9 @@ enum {
BtleScanActive = 2
};
+static constexpr auto deviceDiscoveryStartTimeLimit = std::chrono::seconds{5};
+static constexpr short deviceDiscoveryStartMaxAttempts = 6;
+
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
const QBluetoothAddress &deviceAdapter, QBluetoothDeviceDiscoveryAgent *parent) :
inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
@@ -66,6 +69,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
m_adapterAddress(deviceAdapter),
m_active(NoScanActive),
leScanTimeout(0),
+ deviceDiscoveryStartAttemptsLeft(deviceDiscoveryStartMaxAttempts),
pendingCancel(false),
pendingStart(false),
lowEnergySearchTimeout(25000),
@@ -112,9 +116,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->error(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->error(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) {
@@ -142,13 +179,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->error(lastError);
+ if (setErrorIfPowerOff())
return;
- }
// check Android v23+ permissions
// -> any device search requires android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION
@@ -247,16 +279,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->error(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;
@@ -264,9 +334,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);
-
if (QtAndroidPrivate::androidSdkVersion() < 18) {
qCDebug(QT_BT_ANDROID) << "Skipping Bluetooth Low Energy device scan due to"
"insufficient Android version.";
@@ -276,7 +343,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
emit q->error(lastError);
return;
}
-
+ // LE search only requested or classic discovery failed but lets try LE scan anyway
startLowEnergyScan();
}
}
@@ -285,15 +352,23 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
{
Q_Q(QBluetoothDeviceDiscoveryAgent);
+ pendingStart = false;
+
+ if (deviceDiscoveryStartTimeout)
+ deviceDiscoveryStartTimeout->stop();
+
if (m_active == NoScanActive)
return;
if (m_active == SDPScanActive) {
- if (pendingCancel)
+ if (pendingCancel) {
+ // If we had both a pending cancel and a pending start,
+ // we now have only a pending cancel.
+ // The pending start was canceled above.
return;
+ }
pendingCancel = true;
- pendingStart = false;
bool success = adapter.callMethod<jboolean>("cancelDiscovery");
if (!success) {
lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
@@ -325,16 +400,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->error(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 5e53bf43..ca93d839 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
@@ -164,6 +164,8 @@ private slots:
private:
void startLowEnergyScan();
+ void classicDiscoveryStartFail();
+ bool setErrorIfPowerOff();
DeviceDiscoveryBroadcastReceiver *receiver;
QBluetoothAddress m_adapterAddress;
@@ -171,6 +173,8 @@ private:
QAndroidJniObject adapter;
QAndroidJniObject leScanner;
QTimer *leScanTimeout;
+ QTimer *deviceDiscoveryStartTimeout = nullptr;
+ short deviceDiscoveryStartAttemptsLeft;
bool pendingCancel, pendingStart;
#elif QT_CONFIG(bluez)
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
index 5aab9b4c..f35c43ac 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
@@ -57,6 +57,8 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+static constexpr auto uuidFetchTimeLimit = std::chrono::seconds{4};
+
QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
: error(QBluetoothServiceDiscoveryAgent::NoError),
@@ -285,9 +287,9 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
if (address.isNull() || uuids.isEmpty()) {
if (discoveredDevices.count() == 1) {
Q_Q(QBluetoothServiceDiscoveryAgent);
- QTimer::singleShot(4000, q, [this]() {
+ QTimer::singleShot(uuidFetchTimeLimit, q, [this]() {
this->_q_fetchUuidsTimeout();
- }); // will also call _q_seriveDiscoveryFinished()
+ }); // will also call _q_serviceDiscoveryFinished()
} else {
_q_serviceDiscoveryFinished();
}
@@ -304,7 +306,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
qCDebug(QT_BT_ANDROID) << result;
}
- /* In general there are two uuid events per device.
+ /* In general there may be up-to two uuid events per device.
* We'll wait for the second event to arrive before we process the UUIDs.
* We utilize a timeout to catch cases when the second
* event doesn't arrive at all.
@@ -334,11 +336,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
sdpCache.insert(address, pair);
- //the discovery on the last device cannot immediately finish
- //we have to grant the 2 seconds timeout delay
- if (discoveredDevices.count() == 1) {
+ //we have to grant the timeout delay to allow potential second event to arrive
+ if (discoveredDevices.size() == 1) {
Q_Q(QBluetoothServiceDiscoveryAgent);
- QTimer::singleShot(4000, q, [this]() {
+ QTimer::singleShot(uuidFetchTimeLimit, q, [this]() {
this->_q_fetchUuidsTimeout();
});
return;
@@ -493,14 +494,18 @@ void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QB
void QBluetoothServiceDiscoveryAgentPrivate::_q_fetchUuidsTimeout()
{
- if (sdpCache.isEmpty())
+ // In practice if device list is empty, discovery has been stopped or bluetooth is offline
+ if (discoveredDevices.isEmpty())
return;
- QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
- const QList<QBluetoothAddress> keys = sdpCache.keys();
- for (const QBluetoothAddress &key : keys) {
- pair = sdpCache.take(key);
- populateDiscoveredServices(pair.first, pair.second);
+ // Process remaining services in the cache (these didn't get a second UUID event)
+ if (!sdpCache.isEmpty()) {
+ QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
+ const QList<QBluetoothAddress> keys = sdpCache.keys();
+ for (const QBluetoothAddress &key : keys) {
+ pair = sdpCache.take(key);
+ populateDiscoveredServices(pair.first, pair.second);
+ }
}
Q_ASSERT(sdpCache.isEmpty());
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
index 0527cdde..40321c55 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
@@ -223,6 +223,12 @@ private:
LocalDeviceBroadcastReceiver *localDeviceReceiver = nullptr;
QAndroidJniObject btAdapter;
+ // The sdpCache caches service discovery results while it is running, and is
+ // cleared once finished. The cache is used as we may (or may not) get more accurate
+ // results after the first result. This temporary caching allows to send the
+ // serviceDiscovered() signal once per service and with the most accurate information.
+ // Partial cache clearing may occur already during the scan if the second (more accurate)
+ // scan result is received.
QMap<QBluetoothAddress,QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > > sdpCache;
#endif
diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp
index 60a64375..9da67272 100644
--- a/src/bluetooth/qbluetoothsocket.cpp
+++ b/src/bluetooth/qbluetoothsocket.cpp
@@ -791,6 +791,9 @@ void QBluetoothSocket::close()
Set the socket to use \a socketDescriptor with a type of \a socketType,
which is in state, \a socketState, and mode, \a openMode.
+ The set socket descriptor is considered owned by the QBluetoothSocket
+ and may be e.g. closed once finished.
+
Returns true on success
*/
diff --git a/src/bluetooth/qbluetoothsocket_bluez.cpp b/src/bluetooth/qbluetoothsocket_bluez.cpp
index da92082d..6d27c7a2 100644
--- a/src/bluetooth/qbluetoothsocket_bluez.cpp
+++ b/src/bluetooth/qbluetoothsocket_bluez.cpp
@@ -75,6 +75,10 @@ QBluetoothSocketPrivateBluez::~QBluetoothSocketPrivateBluez()
readNotifier = nullptr;
delete connectWriteNotifier;
connectWriteNotifier = nullptr;
+
+ // If the socket wasn't closed/aborted make sure we free the socket file descriptor
+ if (socket != -1)
+ QT_CLOSE(socket);
}
bool QBluetoothSocketPrivateBluez::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
@@ -658,6 +662,8 @@ qint64 QBluetoothSocketPrivateBluez::readData(char *data, qint64 maxSize)
void QBluetoothSocketPrivateBluez::close()
{
+ // If we have pending data on the write buffer, wait until it has been written,
+ // after which this close() will be called again
if (txBuffer.size() > 0)
connectWriteNotifier->setEnabled(true);
else
diff --git a/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp b/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
index 9e0dfc54..f7db73f6 100644
--- a/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
+++ b/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
@@ -511,7 +511,7 @@ void tst_QBluetoothSocket::tst_preferredSecurityFlags()
#elif QT_CONFIG(bluez)
// The bluezdbus socket uses "NoSecurity" by default, whereas the non-dbus bluez
// socket uses "Authorization" by default
- if (bluetoothdVersion() >= QVersionNumber(5, 42))
+ if (bluetoothdVersion() >= QVersionNumber(5, 46))
QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Security::NoSecurity);
else
QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Security::Authorization);
@@ -577,4 +577,3 @@ void tst_QBluetoothSocket::tst_unsupportedProtocolError()
QTEST_MAIN(tst_QBluetoothSocket)
#include "tst_qbluetoothsocket.moc"
-