diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2023-01-03 10:37:21 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2023-02-24 13:59:28 +0100 |
commit | ae1a1f5efce291f613a13757ff6f744fcca2d2ce (patch) | |
tree | 4d0b562d220783e75d4aa2cc3c5cae1d28fcd4e0 /src | |
parent | 935ec099758487b57970732f0ecfe7021b655968 (diff) |
QtBluetooth: port the code to the new permissions API (CoreBluetooth)
We only _check_ if permissions were granted, leaving it up to an application
to _request_ permissions. If permission status is not 'Granted', we bail
out ealy setting MissionPermissionsError. QLowEnergyController::init is
now a no-op for Darwin, because it's OK to create a peripheral/central and no need
to set any error yet. Autotests require minor adjustments - they were
already passing if BT is off, as it is on CI (checking Bluetooth local
device and its status), but if BT is somewhere on, tests can try to scan or
connect not having permissions granted - for this we adjust the tests,
using permission API.
[ChangeLog][Important Behavior Changes][QtBluetooth][Darwin] Do not request
permissions implicitly, only check them and leave it to applications to request
permissions explicitly.
Task-number: QTBUG-109964
Change-Id: I95c04744e979614ffb6d992da2e279e86b272679
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/bluetooth/darwin/btutility.mm | 18 | ||||
-rw-r--r-- | src/bluetooth/darwin/btutility_p.h | 6 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm | 23 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_macos.mm | 2 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_darwin.mm | 66 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_darwin_p.h | 1 |
6 files changed, 48 insertions, 68 deletions
diff --git a/src/bluetooth/darwin/btutility.mm b/src/bluetooth/darwin/btutility.mm index 706835da..e9f2156f 100644 --- a/src/bluetooth/darwin/btutility.mm +++ b/src/bluetooth/darwin/btutility.mm @@ -33,8 +33,6 @@ const int maxValueLength = 512; const int defaultMtu = 23; -NSString *const bluetoothUsageKey = @"NSBluetoothAlwaysUsageDescription"; - QString qt_address(NSString *address) { if (address && address.length) { @@ -293,22 +291,6 @@ ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray return result; } -bool qt_appNeedsBluetoothUsageDescription() -{ -#ifdef Q_OS_MACOS - return QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur; -#endif - return true; -} - -bool qt_appPlistContainsDescription(NSString *key) -{ - Q_ASSERT(key); - - NSDictionary<NSString *, id> *infoDict = NSBundle.mainBundle.infoDictionary; - return !!infoDict[key]; -} - // A small RAII class for a dispatch queue. class SerialDispatchQueue { diff --git a/src/bluetooth/darwin/btutility_p.h b/src/bluetooth/darwin/btutility_p.h index 193a24f7..87f4a719 100644 --- a/src/bluetooth/darwin/btutility_p.h +++ b/src/bluetooth/darwin/btutility_p.h @@ -123,12 +123,6 @@ extern const int defaultLEScanTimeoutMS; extern const int maxValueLength; extern const int defaultMtu; -// Add more keys if needed, for now this one is enough: -extern NSString *const bluetoothUsageKey; - -bool qt_appNeedsBluetoothUsageDescription(); -bool qt_appPlistContainsDescription(NSString *key); - } // namespace DarwinBluetooth Q_DECLARE_LOGGING_CATEGORY(QT_BT_DARWIN) diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm index b04e3876..0f32898b 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm @@ -25,6 +25,7 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qcoreapplication.h> +#include <QtCore/qpermissions.h> #include <QtCore/qvector.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> @@ -135,17 +136,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent // starting from Monterey. // No Classic on iOS, and Classic does not require a description on macOS: - if (methods.testFlag(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) - && qt_appNeedsBluetoothUsageDescription() - && !qt_appPlistContainsDescription(bluetoothUsageKey)) { - // This would result in Bluetooth framework throwing an exception - // the moment we try to start device discovery. - qCWarning(QT_BT_DARWIN) - << "A proper Info.plist with NSBluetoothAlwaysUsageDescription " - "entry is required, cannot start device discovery"; - setError(QBluetoothDeviceDiscoveryAgent::MissingPermissionsError); - emit q_ptr->errorOccurred(lastError); - return; + if (methods.testFlag(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) { + const auto permissionStatus = qApp->checkPermission(QBluetoothPermission{}); + if (permissionStatus != Qt::PermissionStatus::Granted) { + qCWarning(QT_BT_DARWIN, + "Use of Bluetooth LE requires explicitly requested permissions."); + setError(QBluetoothDeviceDiscoveryAgent::MissingPermissionsError); + emit q_ptr->errorOccurred(lastError); + // Arguably, Classic scan is still possible, but let's keep the logic + // simple. + return; + } } requestedMethods = methods; diff --git a/src/bluetooth/qbluetoothlocaldevice_macos.mm b/src/bluetooth/qbluetoothlocaldevice_macos.mm index 7063b357..3d1da5ea 100644 --- a/src/bluetooth/qbluetoothlocaldevice_macos.mm +++ b/src/bluetooth/qbluetoothlocaldevice_macos.mm @@ -415,7 +415,7 @@ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() QBluetoothLocalDevice defaultAdapter; if (!defaultAdapter.isValid() || defaultAdapter.address().isNull()) { - qCCritical(QT_BT_DARWIN) << Q_FUNC_INFO <<"no valid device found"; + qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO <<"no valid device found"; return localDevices; } diff --git a/src/bluetooth/qlowenergycontroller_darwin.mm b/src/bluetooth/qlowenergycontroller_darwin.mm index 8b2646cf..3f7fb215 100644 --- a/src/bluetooth/qlowenergycontroller_darwin.mm +++ b/src/bluetooth/qlowenergycontroller_darwin.mm @@ -21,6 +21,8 @@ #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qlist.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qpermissions.h> QT_BEGIN_NAMESPACE @@ -113,37 +115,44 @@ bool QLowEnergyControllerPrivateDarwin::isValid() const void QLowEnergyControllerPrivateDarwin::init() { + // We have to override the 'init', it's pure virtual in the base. + // Just creating a central or peripheral should not trigger any + // error yet. +} + +bool QLowEnergyControllerPrivateDarwin::lazyInit() +{ using namespace DarwinBluetooth; - if (qt_appNeedsBluetoothUsageDescription() && !qt_appPlistContainsDescription(bluetoothUsageKey)) { - qCWarning(QT_BT_DARWIN) - << "The Info.plist file is required to contain " - "'NSBluetoothAlwaysUsageDescription' entry"; - return; + if (peripheralManager || centralManager) + return true; + + if (qApp->checkPermission(QBluetoothPermission{}) != Qt::PermissionStatus::Granted) { + qCWarning(QT_BT_DARWIN, + "Use of Bluetooth LE must be explicitly requested by the application."); + setError(QLowEnergyController::MissingPermissionsError); + return false; } std::unique_ptr<LECBManagerNotifier> notifier = std::make_unique<LECBManagerNotifier>(); if (role == QLowEnergyController::PeripheralRole) { peripheralManager.reset([[DarwinBTPeripheralManager alloc] initWith:notifier.get()], DarwinBluetooth::RetainPolicy::noInitialRetain); - if (!peripheralManager) { - qCWarning(QT_BT_DARWIN) << "failed to create a peripheral manager"; - return; - } + Q_ASSERT(peripheralManager); } else { centralManager.reset([[DarwinBTCentralManager alloc] initWith:notifier.get()], DarwinBluetooth::RetainPolicy::noInitialRetain); - if (!centralManager) { - qCWarning(QT_BT_DARWIN) << "failed to initialize a central manager"; - return; - } + Q_ASSERT(centralManager); } + // FIXME: Q_UNLIKELY if (!connectSlots(notifier.get())) qCWarning(QT_BT_DARWIN) << "failed to connect to notifier's signal(s)"; // Ownership was taken by central manager. notifier.release(); + + return true; } void QLowEnergyControllerPrivateDarwin::connectToDevice() @@ -151,24 +160,14 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice() Q_ASSERT_X(state == QLowEnergyController::UnconnectedState, Q_FUNC_INFO, "invalid state"); - if (qt_appNeedsBluetoothUsageDescription() - && !qt_appPlistContainsDescription(bluetoothUsageKey)) { - qCWarning(QT_BT_DARWIN) - << "The Info.plist file is required to contain " - "'NSBluetoothAlwaysUsageDescription' entry"; - return _q_CBManagerError(QLowEnergyController::MissingPermissionsError); - } - - if (!isValid()) { - // init() had failed or was never called. - return _q_CBManagerError(QLowEnergyController::UnknownError); - } - if (deviceUuid.isNull()) { // Wrong constructor was used or invalid UUID was provided. return _q_CBManagerError(QLowEnergyController::UnknownRemoteDeviceError); } + if (!lazyInit()) // MissingPermissionsError was emit. + return; + // The logic enforcing the role is in the public class. Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, Q_FUNC_INFO, "invalid role (peripheral)"); @@ -188,13 +187,12 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice() void QLowEnergyControllerPrivateDarwin::disconnectFromDevice() { - Q_ASSERT(isValid()); // Check for proper state is in q's code. + Q_ASSERT(isValid()); // Check for proper state is in Qt's code. if (role == QLowEnergyController::PeripheralRole) { // CoreBluetooth API intentionally does not provide any way of closing // a connection. All we can do here is to stop the advertisement. - stopAdvertising(); - return; + return stopAdvertising(); } const auto oldState = state; @@ -287,7 +285,13 @@ void QLowEnergyControllerPrivateDarwin::addToGenericAttributeList(const QLowEner int QLowEnergyControllerPrivateDarwin::mtu() const { + // FIXME: check the state - neither public class does, + // nor us - not fun! E.g. readRssi correctly checked/asserted. + __block int mtu = DarwinBluetooth::defaultMtu; + if (!isValid()) // A minimal check. + return defaultMtu; + if (const auto leQueue = DarwinBluetooth::qt_LE_queue()) { const auto *manager = centralManager.getAs<DarwinBTCentralManager>(); dispatch_sync(leQueue, ^{ @@ -1001,10 +1005,8 @@ void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdverti return; } - if (!isValid()) { - qCWarning(QT_BT_DARWIN, "LE controller is an invalid peripheral"); + if (!lazyInit()) // Error was emit already. return; - } if (state != QLowEnergyController::UnconnectedState) { qCWarning(QT_BT_DARWIN) << "invalid state" << state; diff --git a/src/bluetooth/qlowenergycontroller_darwin_p.h b/src/bluetooth/qlowenergycontroller_darwin_p.h index d156d785..2782ff59 100644 --- a/src/bluetooth/qlowenergycontroller_darwin_p.h +++ b/src/bluetooth/qlowenergycontroller_darwin_p.h @@ -43,6 +43,7 @@ public: ~QLowEnergyControllerPrivateDarwin(); void init() override; + bool lazyInit(); void connectToDevice() override; void disconnectFromDevice() override; void discoverServices() override; |