diff options
Diffstat (limited to 'src')
8 files changed, 135 insertions, 42 deletions
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 |