Fix QBluetoothDeviceInfo::isCached() on Windows

The Windows backend was unconditionally marking each discovered device
as cached. However, a correct thing to do it to only mark those devices
that come from the OS cache.
For Windows these are always the paired devices.

However, there's one caveat here as well. If the device is cached, but
at the same time actually available, Windows will first report it with
rssi == 0 (at least in case of BTLE devices), and only then deliver an
update with the correct RSSI value.

This patch implements the following logic:
* All newly-discovered paired devices are marked as cached first
* Once there's an update, the device is not considered as cached
  anymore.

This works fine for BTLE devices, because they regularly report at
least the RSSI updates.

For Classic Bluetooth we don't have any device updates during the
discovery, so simply rely on the pairing status at the time of
initial discovery.

Fixes: QTBUG-140825
Pick-to: 6.10 6.8
Change-Id: Ifd9ca57fe837522be01555984637cea65094900c
Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
This commit is contained in:
Ivan Solovev 2025-10-08 14:04:14 +02:00
parent 00ef8813c4
commit 42b3dd064d
1 changed files with 29 additions and 6 deletions

View File

@ -213,7 +213,8 @@ private:
void getClassicDeviceFromId(const winrt::hstring &id); void getClassicDeviceFromId(const winrt::hstring &id);
void handleClassicDevice(const BluetoothDevice &device); void handleClassicDevice(const BluetoothDevice &device);
void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult, void handleRfcommServices(const RfcommDeviceServicesResult &servicesResult,
uint64_t address, const QString &name, uint32_t classOfDeviceInt); uint64_t address, const QString &name, uint32_t classOfDeviceInt,
bool isCached);
// Bluetooth Low Energy handlers // Bluetooth Low Energy handlers
void getLowEnergyDeviceFromId(const winrt::hstring &id); void getLowEnergyDeviceFromId(const winrt::hstring &id);
@ -538,14 +539,23 @@ void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDe
const std::wstring name { device.Name() }; // via operator std::wstring_view() const std::wstring name { device.Name() }; // via operator std::wstring_view()
const QString btName = QString::fromStdWString(name); const QString btName = QString::fromStdWString(name);
const uint32_t deviceClass = device.ClassOfDevice().RawValue(); const uint32_t deviceClass = device.ClassOfDevice().RawValue();
// Use IsPaired() to determine if the device is cached or not.
// That is because Windows will always report paired devices, even if
// they are not physically available. This is a sub-optimal approach,
// but looks like we cannot do better...
const bool isCached = device.DeviceInformation().Pairing().IsPaired();
auto thisPtr = shared_from_this(); auto thisPtr = shared_from_this();
auto asyncOp = device.GetRfcommServicesAsync(); auto asyncOp = device.GetRfcommServicesAsync();
asyncOp.Completed([thisPtr, address, btName, deviceClass](auto &&op, AsyncStatus status) { asyncOp.Completed([thisPtr, address, btName, deviceClass, isCached]
(auto &&op, AsyncStatus status) {
if (thisPtr) { if (thisPtr) {
if (status == AsyncStatus::Completed) { if (status == AsyncStatus::Completed) {
auto servicesResult = op.GetResults(); auto servicesResult = op.GetResults();
if (servicesResult) { if (servicesResult) {
thisPtr->handleRfcommServices(servicesResult, address, btName, deviceClass); thisPtr->handleRfcommServices(servicesResult, address, btName,
deviceClass, isCached);
return; return;
} }
} }
@ -559,7 +569,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::handleClassicDevice(const BluetoothDe
// this is a callback - executes in a new thread // this is a callback - executes in a new thread
void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices( void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
const RfcommDeviceServicesResult &servicesResult, uint64_t address, const RfcommDeviceServicesResult &servicesResult, uint64_t address,
const QString &name, uint32_t classOfDeviceInt) const QString &name, uint32_t classOfDeviceInt, bool isCached)
{ {
// need to perform the check even if some of the operations fails // need to perform the check even if some of the operations fails
auto shared = shared_from_this(); auto shared = shared_from_this();
@ -591,7 +601,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::handleRfcommServices(
QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt); QBluetoothDeviceInfo info(btAddress, name, classOfDeviceInt);
info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); info.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration);
info.setServiceUuids(uuids); info.setServiceUuids(uuids);
info.setCached(true); info.setCached(isCached);
QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection, QMetaObject::invokeMethod(this, "deviceFound", Qt::AutoConnection,
Q_ARG(QBluetoothDeviceInfo, info)); Q_ARG(QBluetoothDeviceInfo, info));
@ -652,7 +662,17 @@ void QWinRTBluetoothDeviceDiscoveryWorker::handleLowEnergyDevice(const Bluetooth
info.setManufacturerData(key, manufacturerData.value(key)); info.setManufacturerData(key, manufacturerData.value(key));
for (QBluetoothUuid key : serviceData.keys()) for (QBluetoothUuid key : serviceData.keys())
info.setServiceData(key, serviceData.value(key)); info.setServiceData(key, serviceData.value(key));
info.setCached(true);
// Now we need to figure out if the device is cached or not.
// Theoretically, we could use the "System.Devices.Aep.IsPresent"
// device property. However, in practice it's not reported, so
// let's try to do our best and estimate the value.
// If the device is paired, then Windows will always report it,
// no matter if it's actually present or not.
// So, treat paired devices as cached at first. If the device is
// actually available, we'll get an update (at least with a new
// rssi), and update the cache state at that point.
info.setCached(isPaired);
// Use the services obtained from the advertisement data if the device is not paired // Use the services obtained from the advertisement data if the device is not paired
if (!isPaired) { if (!isPaired) {
@ -831,6 +851,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::updateDeviceData(const QBluetoothAdd
if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData)) if (fields.testFlag(QBluetoothDeviceInfo::Field::ServiceData))
for (QBluetoothUuid key : serviceData.keys()) for (QBluetoothUuid key : serviceData.keys())
iter->setServiceData(key, serviceData.value(key)); iter->setServiceData(key, serviceData.value(key));
// We got some data, so the device is definitely available now.
// Update the cached state.
iter->setCached(false);
emit q->deviceUpdated(*iter, fields); emit q->deviceUpdated(*iter, fields);
return; return;
} }