summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2021-11-09 12:19:21 +0100
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2021-11-24 16:31:59 +0100
commit1e76f0f9af3db8cb7299d0f8cf9df7876cbb3e0a (patch)
tree251a70d9e9c0d3ccb4f704ae9c26c1af1f799d7b
parentbf69c830419a7cda87d6aa71c962804a946011ea (diff)
downloadqtconnectivity-1e76f0f9af3db8cb7299d0f8cf9df7876cbb3e0a.tar.gz
Bluetooth (Darwin): bail out early, if Info.plist is missing
or not having proper entries that required on iOS and (starting from Monterey) on macOS (but LE only). Without them, app crashes due to exception thrown by the framework. Task-number: QTBUG-97900 Task-number: QTBUG-96557 Change-Id: Ia1463fa4361884936594780312586eb17f8fe075 Reviewed-by: Juha Vuolle <juha.vuolle@insta.fi> (cherry picked from commit dfa20ef097459587ef9086dd6f4edd313123676b)
-rw-r--r--src/bluetooth/osx/osxbtutility.mm19
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h6
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm21
-rw-r--r--src/bluetooth/qlowenergycontroller_darwin.mm65
-rw-r--r--src/bluetooth/qlowenergycontroller_darwin_p.h5
5 files changed, 91 insertions, 25 deletions
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index de1a3044..091090a5 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -42,6 +42,7 @@
#include "osxbtutility_p.h"
#include "qbluetoothuuid.h"
+#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/qendian.h>
#include <QtCore/qstring.h>
@@ -76,6 +77,8 @@ const int defaultLEScanTimeoutMS = 25000;
// We use it only on iOS for now:
const int maxValueLength = 512;
+NSString *const bluetoothUsageKey = @"NSBluetoothAlwaysUsageDescription";
+
QString qt_address(NSString *address)
{
if (address && address.length) {
@@ -351,6 +354,22 @@ 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/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index 438fcd90..b285e252 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -307,6 +307,12 @@ dispatch_queue_t qt_LE_queue();
extern const int defaultLEScanTimeoutMS;
extern const int maxValueLength;
+// Add more keys if needed, for now this one is enough:
+extern NSString *const bluetoothUsageKey;
+
+bool qt_appNeedsBluetoothUsageDescription();
+bool qt_appPlistContainsDescription(NSString *key);
+
} // namespace OSXBluetooth
// Logging category for both OS X and iOS.
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm
index 44db3a6e..aaf519af 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm
@@ -144,6 +144,8 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods)
{
+ using namespace OSXBluetooth;
+
Q_ASSERT(!isActive());
Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError);
Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod
@@ -157,6 +159,25 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
}
#endif // Q_OS_MACOS
+ // To be able to scan for devices, iOS requires Info.plist containing
+ // NSBluetoothAlwaysUsageDescription entry with a string, explaining
+ // the usage of Bluetooth interface. macOS also requires this description,
+ // 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_OSX)
+ << "A proper Info.plist with NSBluetoothAlwaysUsageDescription "
+ "entry is required, cannot start device discovery";
+ setError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod);
+ emit q_ptr->error(lastError);
+ return;
+ }
+
requestedMethods = methods;
if (stopPending) {
diff --git a/src/bluetooth/qlowenergycontroller_darwin.mm b/src/bluetooth/qlowenergycontroller_darwin.mm
index 78fae409..efac1f1a 100644
--- a/src/bluetooth/qlowenergycontroller_darwin.mm
+++ b/src/bluetooth/qlowenergycontroller_darwin.mm
@@ -165,7 +165,14 @@ bool QLowEnergyControllerPrivateDarwin::isValid() const
void QLowEnergyControllerPrivateDarwin::init()
{
- using OSXBluetooth::LECBManagerNotifier;
+ using namespace OSXBluetooth;
+
+ if (qt_appNeedsBluetoothUsageDescription() && !qt_appPlistContainsDescription(bluetoothUsageKey)) {
+ qCWarning(QT_BT_OSX)
+ << "The Info.plist file is required to contain"
+ "'NSBluetoothAlwaysUsageDescription' entry";
+ return;
+ }
QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier);
if (role == QLowEnergyController::PeripheralRole) {
@@ -202,7 +209,7 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice()
Q_FUNC_INFO, "invalid state");
if (!isValid()) {
- // init() had failed for was never called.
+ // init() had failed or was never called.
return _q_CBManagerError(QLowEnergyController::UnknownError);
}
@@ -234,6 +241,8 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice()
void QLowEnergyControllerPrivateDarwin::disconnectFromDevice()
{
+ Q_ASSERT(isValid()); // Check for proper state is in q'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.
@@ -241,29 +250,28 @@ void QLowEnergyControllerPrivateDarwin::disconnectFromDevice()
return;
}
- if (isValid()) {
- const auto oldState = state;
+ const auto oldState = state;
- if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
- setState(QLowEnergyController::ClosingState);
- invalidateServices();
- auto manager = centralManager.getAs<ObjCCentralManager>();
- dispatch_async(leQueue, ^{
- [manager disconnectFromDevice];
- });
+ if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
+ setState(QLowEnergyController::ClosingState);
+ invalidateServices();
- if (oldState == QLowEnergyController::ConnectingState) {
- // With a pending connect attempt there is no
- // guarantee we'll ever have didDisconnect callback,
- // set the state here and now to make sure we still
- // can connect.
- setState(QLowEnergyController::UnconnectedState);
- }
- } else {
- qCCritical(QT_BT_OSX) << "qt LE queue is nil, "
- "can not dispatch 'disconnect'";
+ auto manager = centralManager.getAs<ObjCCentralManager>();
+ dispatch_async(leQueue, ^{
+ [manager disconnectFromDevice];
+ });
+
+ if (oldState == QLowEnergyController::ConnectingState) {
+ // With a pending connect attempt there is no
+ // guarantee we'll ever have didDisconnect callback,
+ // set the state here and now to make sure we still
+ // can connect.
+ setState(QLowEnergyController::UnconnectedState);
}
+ } else {
+ qCCritical(QT_BT_OSX) << "qt LE queue is nil, "
+ "can not dispatch 'disconnect'";
}
}
@@ -274,6 +282,8 @@ void QLowEnergyControllerPrivateDarwin::discoverServices()
Q_ASSERT_X(role != QLowEnergyController::PeripheralRole,
Q_FUNC_INFO, "invalid role (peripheral)");
+ Q_ASSERT(isValid()); // Check we're in a proper state is in q's code.
+
dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
Q_ASSERT_X(leQueue, Q_FUNC_INFO, "LE queue not found");
@@ -334,6 +344,11 @@ QLowEnergyService * QLowEnergyControllerPrivateDarwin::addServiceHelper(const QL
Q_UNUSED(service);
qCDebug(QT_BT_OSX, "peripheral role is not supported on tvOS");
#else
+ if (!isValid()) {
+ qCWarning(QT_BT_OSX) << "invalid peripheral";
+ return nullptr;
+ }
+
if (role != QLowEnergyController::PeripheralRole) {
qCWarning(QT_BT_OSX) << "not in peripheral role";
return nullptr;
@@ -1040,14 +1055,16 @@ void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdverti
qCWarning(QT_BT_OSX) << "advertising is not supported on your platform";
#else
- if (!isValid())
- return _q_CBManagerError(QLowEnergyController::UnknownError);
-
if (role != QLowEnergyController::PeripheralRole) {
qCWarning(QT_BT_OSX) << "controller is not a peripheral, cannot start advertising";
return;
}
+ if (!isValid()) {
+ qCWarning(QT_BT_OSX, "LE controller is an invalid peripheral");
+ return;
+ }
+
if (state != QLowEnergyController::UnconnectedState) {
qCWarning(QT_BT_OSX) << "invalid state" << state;
return;
diff --git a/src/bluetooth/qlowenergycontroller_darwin_p.h b/src/bluetooth/qlowenergycontroller_darwin_p.h
index 7d82bfc9..03880e01 100644
--- a/src/bluetooth/qlowenergycontroller_darwin_p.h
+++ b/src/bluetooth/qlowenergycontroller_darwin_p.h
@@ -108,7 +108,10 @@ public:
const QLowEnergyAdvertisingData &scanResponseData) override;
void stopAdvertising()override;
QLowEnergyService *addServiceHelper(const QLowEnergyServiceData &service) override;
- bool isValid() const; // QT6 - delete this logic.
+
+ // Valid - a central or peripheral instance was allocated, and this may also
+ // mean a proper usage description was provided/found:
+ bool isValid() const;
private Q_SLOTS:
void _q_connected();