summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2015-11-24 10:28:01 +0100
committerTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2015-11-27 14:19:42 +0000
commite117459e0d3d0670aa2cc60dfa3eafe81bbf11a9 (patch)
tree14a495bd997005b9f76e85919d4792adf5a084cf
parentdab0c9a010cf7fcd521707168cc8107b7ed6af24 (diff)
downloadqtconnectivity-e117459e0d3d0670aa2cc60dfa3eafe81bbf11a9.tar.gz
Bluetooth LE scan - move to its own dispatch queue (iOS/OS X)
Move CBCentralManager's delegate to its own dispatch queue, not the main one, so that even if somebody _blocks_ the main thread somehow, waiting for discovery to finish, the discovery can, indeed, finish. Also, make DDA aware of the fact that now actual scan and 'this' are working on different threads, thus we must be thread-safe. Task-number: QTBUG-49476 Change-Id: I9bcc74131f51389c8a014577c65e0943bbc8da42 Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com> Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
-rw-r--r--src/bluetooth/bluetooth.pro3
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm325
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry_p.h73
-rw-r--r--src/bluetooth/osx/osxbtutility.mm41
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h4
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm231
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm168
7 files changed, 452 insertions, 393 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index e37ad7aa..6cf0795c 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -164,7 +164,8 @@ config_bluez:qtHaveModule(dbus) {
qlowenergyservice_osx.mm
PRIVATE_HEADERS += \
- qlowenergycontroller_osx_p.h
+ qlowenergycontroller_osx_p.h \
+ qbluetoothdevicediscoverytimer_osx_p.h
include(osx/osxbt.pri)
SOURCES += \
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 28bfd1bc..f3a95820 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -46,10 +46,6 @@ QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
-LEDeviceInquiryDelegate::~LEDeviceInquiryDelegate()
-{
-}
-
#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_6_0)
QBluetoothUuid qt_uuid(NSUUID *nsUuid)
@@ -107,32 +103,19 @@ using namespace QT_NAMESPACE;
#endif
-@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate, CBPeripheralDelegate>
-// "Timeout" callback to stop a scan.
+@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) (PrivateAPI) <CBCentralManagerDelegate>
- (void)stopScan;
-- (void)handlePoweredOffAfterDelay;
+- (void)handlePoweredOff;
@end
@implementation QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry)
-+ (int)inquiryLength
-{
- // There is no default timeout,
- // scan does not stop if not asked.
- // Return in milliseconds
- return 10 * 1000;
-}
-
-- (id)initWithDelegate:(OSXBluetooth::LEDeviceInquiryDelegate *)aDelegate
+- (id)init
{
- Q_ASSERT_X(aDelegate, Q_FUNC_INFO, "invalid delegate (null)");
-
if (self = [super init]) {
- delegate = aDelegate;
- peripherals = [[NSMutableDictionary alloc] init];
- manager = nil;
- scanPhase = noActivity;
- cancelled = false;
+ uuids.reset([[NSMutableSet alloc] init]);
+ internalState = InquiryStarting;
+ state.store(int(internalState));
}
return self;
@@ -140,150 +123,137 @@ using namespace QT_NAMESPACE;
- (void)dealloc
{
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
-
if (manager) {
[manager setDelegate:nil];
- if (scanPhase == activeScan)
+ if (internalState == InquiryActive)
[manager stopScan];
- [manager release];
}
- [peripherals release];
[super dealloc];
}
- (void)stopScan
{
- // Scan's timeout.
- Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
- Q_ASSERT_X(manager, Q_FUNC_INFO, "invalid central (nil)");
- Q_ASSERT_X(scanPhase == activeScan, Q_FUNC_INFO, "invalid state");
- Q_ASSERT_X(!cancelled, Q_FUNC_INFO, "invalid state");
-
- [manager setDelegate:nil];
- [manager stopScan];
- scanPhase = noActivity;
-
- delegate->LEdeviceInquiryFinished();
-}
+ // Scan's "timeout" - we consider LE device
+ // discovery finished.
+ using namespace OSXBluetooth;
-- (void)handlePoweredOffAfterDelay
-{
- // If we are here, this means:
- // we received 'PoweredOff' while scanPhase == startingScan
- // and no 'PoweredOn' after this.
-
- Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
- Q_ASSERT_X(scanPhase == startingScan, Q_FUNC_INFO, "invalid state");
-
- scanPhase = noActivity;
- if (cancelled) {
- // Timeout happened before
- // the second status update, but after 'stop'.
- delegate->LEdeviceInquiryFinished();
- } else {
- // Timeout and no 'stop' between 'start'
- // and 'centralManagerDidUpdateStatus':
- delegate->LEnotSupported();
+ if (internalState == InquiryActive) {
+ if (scanTimer.elapsed() >= qt_LE_deviceInquiryLength() * 1000) {
+ // We indeed stop now:
+ [manager stopScan];
+ [manager setDelegate:nil];
+ internalState = InquiryFinished;
+ state.store(int(internalState));
+ } else {
+ dispatch_queue_t leQueue(qt_LE_queue());
+ Q_ASSERT(leQueue);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self stopScan];
+ });
+ }
}
}
-- (bool)start
+- (void)handlePoweredOff
{
- Q_ASSERT_X(![self isActive], Q_FUNC_INFO, "LE device scan is already active");
- Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
-
- if (!peripherals) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "internal error";
- return false;
- }
-
- cancelled = false;
- [peripherals removeAllObjects];
+ // This is interesting on iOS only, where
+ // the system shows an alert asking to enable
+ // Bluetooth in the 'Settings' app. If not done yet (after 30
+ // seconds) - we consider it an error.
+ if (internalState == InquiryStarting) {
+ if (errorTimer.elapsed() >= 30000) {
+ [manager setDelegate:nil];
+ internalState = ErrorPoweredOff;
+ state.store(int(internalState));
+ } else {
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT(leQueue);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self handlePoweredOff];
+ });
- if (manager) {
- // We can never be here, if status was not updated yet.
- [manager setDelegate:nil];
- [manager release];
+ }
}
+}
- startTime = QTime();
- scanPhase = startingScan;
- manager = [CBCentralManager alloc];
- manager = [manager initWithDelegate:self queue:nil];
- if (!manager) {
- qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to create a central manager";
- return false;
- }
+- (void)start
+{
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
- return true;
+ Q_ASSERT(leQueue);
+ manager.reset([[CBCentralManager alloc] initWithDelegate:self queue:leQueue]);
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
- Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
-
- const CBCentralManagerState state = central.state;
-
- if (scanPhase == startingScan && (state == CBCentralManagerStatePoweredOn
- || state == CBCentralManagerStateUnsupported
- || state == CBCentralManagerStateUnauthorized
- || state == CBCentralManagerStatePoweredOff)) {
- // We probably had 'PoweredOff' before,
- // cancel the previous handlePoweredOffAfterDelay.
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- }
+ if (central != manager)
+ return;
- if (cancelled) {
- Q_ASSERT_X(scanPhase != activeScan, Q_FUNC_INFO, "in 'activeScan' phase");
- scanPhase = noActivity;
- delegate->LEdeviceInquiryFinished();
+ if (internalState != InquiryActive && internalState != InquiryStarting)
return;
- }
- if (state == CBCentralManagerStatePoweredOn) {
- if (scanPhase == startingScan) {
- scanPhase = activeScan;
-#ifndef Q_OS_OSX
- const NSTimeInterval timeout([QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) inquiryLength] / 1000);
- Q_ASSERT_X(timeout > 0., Q_FUNC_INFO, "invalid scan timeout");
- [self performSelector:@selector(stopScan) withObject:nil afterDelay:timeout];
-#endif
- startTime = QTime::currentTime();
+ using namespace OSXBluetooth;
+
+ dispatch_queue_t leQueue(qt_LE_queue());
+ Q_ASSERT(leQueue);
+
+ const CBCentralManagerState cbState(central.state);
+ if (cbState == CBCentralManagerStatePoweredOn) {
+ if (internalState == InquiryStarting) {
+ internalState = InquiryActive;
+ // Scan time is actually 10 seconds. Having a block with such delay can prevent
+ // 'self' from being deleted in time, which is not good. So we split this
+ // 10 s. timeout into smaller 'chunks'.
+ scanTimer.start();
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ int64_t(qt_LE_deviceInquiryLength() / 100. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self stopScan];
+ });
[manager scanForPeripheralsWithServices:nil options:nil];
} // Else we ignore.
} else if (state == CBCentralManagerStateUnsupported || state == CBCentralManagerStateUnauthorized) {
- if (scanPhase == startingScan) {
- scanPhase = noActivity;
- delegate->LEnotSupported();
- } else if (scanPhase == activeScan) {
- // Cancel stopScan:
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
-
- scanPhase = noActivity;
+ if (internalState == InquiryActive) {
[manager stopScan];
- delegate->LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+ // Not sure how this is possible at all, probably, can never happen.
+ internalState = ErrorPoweredOff;
+ } else {
+ internalState = ErrorLENotSupported;
}
- } else if (state == CBCentralManagerStatePoweredOff) {
- if (scanPhase == startingScan) {
+
+ [manager setDelegate:nil];
+ } else if (cbState == CBCentralManagerStatePoweredOff) {
+ if (internalState == InquiryStarting) {
#ifndef Q_OS_OSX
// On iOS a user can see at this point an alert asking to enable
// Bluetooth in the "Settings" app. If a user does,
// we'll receive 'PoweredOn' state update later.
- [self performSelector:@selector(handlePoweredOffAfterDelay) withObject:nil afterDelay:30.];
+ // No change in state. Wait for 30 seconds (we split it into 'chunks' not
+ // to retain 'self' for too long ) ...
+ errorTimer.start();
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
+ (int64_t)(30 / 100. * NSEC_PER_SEC)),
+ leQueue,
+ ^{
+ [self handlePoweredOff];
+ });
return;
#endif
- scanPhase = noActivity;
- delegate->LEnotSupported();
- } else if (scanPhase == activeScan) {
- // Cancel stopScan:
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
-
- scanPhase = noActivity;
+ internalState = ErrorPoweredOff;
+ } else {
+ internalState = ErrorPoweredOff;
[manager stopScan];
- delegate->LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
- } // Else we ignore.
+ }
+
+ [manager setDelegate:nil];
} else {
// The following two states we ignore (from Apple's docs):
//"
@@ -294,44 +264,36 @@ using namespace QT_NAMESPACE;
// -CBCentralManagerStateResetting
// The connection with the system service was momentarily
// lost; an update is imminent. "
+ // Wait for this imminent update.
}
+
+ state.store(int(internalState));
}
- (void)stop
{
- if (scanPhase != startingScan) {
- // startingScan means either no selector at all,
- // or handlePoweredOffAfterDelay and we do not want to cancel it yet,
- // waiting for DidUpdateState or handlePoweredOffAfterDelay, whoever
- // fires first ...
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- }
-
- if (scanPhase == startingScan || cancelled) {
- // We have to wait for a status update or handlePoweredOffAfterDelay.
- cancelled = true;
- return;
- }
-
- if (scanPhase == activeScan) {
+ if (internalState == InquiryActive)
[manager stopScan];
- scanPhase = noActivity;
- delegate->LEdeviceInquiryFinished();
- }
+
+ [manager setDelegate:nil];
+ internalState = InquiryCancelled;
+ state.store(int(internalState));
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
- Q_UNUSED(central)
- Q_UNUSED(advertisementData)
+ Q_UNUSED(advertisementData);
using namespace OSXBluetooth;
- if (scanPhase != activeScan)
+ if (central != manager)
return;
- Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ if (internalState != InquiryActive)
+ return;
+
+ QBluetoothUuid deviceUuid;
#if QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_9, __IPHONE_7_0)
if (QSysInfo::MacintoshVersion >= qt_OS_limit(QSysInfo::MV_10_9, QSysInfo::MV_IOS_7_0)) {
@@ -340,45 +302,64 @@ using namespace QT_NAMESPACE;
return;
}
- if (![peripherals objectForKey:peripheral.identifier]) {
- [peripherals setObject:peripheral forKey:peripheral.identifier];
- const QBluetoothUuid deviceUuid(OSXBluetooth::qt_uuid(peripheral.identifier));
- delegate->LEdeviceFound(peripheral, deviceUuid, advertisementData, RSSI);
+ if ([uuids containsObject:peripheral.identifier]) {
+ // We already know this peripheral ...
+ return;
}
- return;
+
+ [uuids addObject:peripheral.identifier];
+ deviceUuid = OSXBluetooth::qt_uuid(peripheral.identifier);
}
#endif
// Either SDK or the target is below 10.9/7.0:
// The property UUID was finally removed in iOS 9, we have
// to avoid compilation errors ...
- CFUUIDRef cfUUID = Q_NULLPTR;
+ if (deviceUuid.isNull()) {
+ CFUUIDRef cfUUID = Q_NULLPTR;
+
+ if ([peripheral respondsToSelector:@selector(UUID)]) {
+ // This will require a bridged cast if we switch to ARC ...
+ cfUUID = reinterpret_cast<CFUUIDRef>([peripheral performSelector:@selector(UUID)]);
+ }
- if ([peripheral respondsToSelector:@selector(UUID)]) {
- // This will require a bridged cast if we switch to ARC ...
- cfUUID = reinterpret_cast<CFUUIDRef>([peripheral performSelector:@selector(UUID)]);
+ if (!cfUUID) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without CFUUID";
+ return;
+ }
+
+ StringStrongReference key(uuid_as_nsstring(cfUUID));
+ if ([uuids containsObject:key.data()])
+ return; // We've seen this peripheral before ...
+ [uuids addObject:key.data()];
+ deviceUuid = OSXBluetooth::qt_uuid(cfUUID);
}
- if (!cfUUID) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "peripheral without CFUUID";
+ if (deviceUuid.isNull()) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "no way to address peripheral, QBluetoothUuid is null";
return;
}
- StringStrongReference key(uuid_as_nsstring(cfUUID));
- if (![peripherals objectForKey:key.data()]) {
- [peripherals setObject:peripheral forKey:key.data()];
- const QBluetoothUuid deviceUuid(OSXBluetooth::qt_uuid(cfUUID));
- delegate->LEdeviceFound(peripheral, deviceUuid, advertisementData, RSSI);
- }
+ QString name;
+ if (peripheral.name)
+ name = QString::fromNSString(peripheral.name);
+
+ // TODO: fix 'classOfDevice' (0 for now).
+ QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
+ if (RSSI)
+ newDeviceInfo.setRssi([RSSI shortValue]);
+ // CoreBluetooth scans only for LE devices.
+ newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
+ devices.append(newDeviceInfo);
}
-- (bool)isActive
+- (LEInquiryState) inquiryState
{
- return scanPhase == startingScan || scanPhase == activeScan;
+ return LEInquiryState(state.load());
}
-- (const QTime&)startTime
+- (const QList<QBluetoothDeviceInfo> &)discoveredDevices
{
- return startTime;
+ return devices;
}
@end
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry_p.h b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
index cb86cd14..9ca299ea 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry_p.h
+++ b/src/bluetooth/osx/osxbtledeviceinquiry_p.h
@@ -46,80 +46,59 @@
//
#include "qbluetoothdevicediscoveryagent.h"
+#include "qbluetoothdeviceinfo.h"
+#include "osxbtutility_p.h"
-#include <QtCore/qdatetime.h>
+#include <QtCore/qelapsedtimer.h>
#include <QtCore/qglobal.h>
+#include <QtCore/qatomic.h>
#include <QtCore/qlist.h>
#include <Foundation/Foundation.h>
-@class QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry);
-
@class CBCentralManager;
@class CBPeripheral;
QT_BEGIN_NAMESPACE
-class QBluetoothDeviceInfo;
class QBluetoothUuid;
-namespace OSXBluetooth {
-
-class LEDeviceInquiryDelegate
-{
-public:
- typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
-
- virtual ~LEDeviceInquiryDelegate();
-
- // At the moment the only error we're reporting is PoweredOffError!
- virtual void LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error) = 0;
-
- virtual void LEnotSupported() = 0;
- virtual void LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &uuid,
- NSDictionary *advertisementData, NSNumber *RSSI) = 0;
- virtual void LEdeviceInquiryFinished() = 0;
-};
-
-}
-
QT_END_NAMESPACE
-// Bluetooth Low Energy scan for iOS and OS X.
-// Strong enum would be quite handy ...
-enum LEScanPhase
+enum LEInquiryState
{
- noActivity,
- startingScan,
- activeScan
+ InquiryStarting,
+ InquiryActive,
+ InquiryFinished,
+ InquiryCancelled,
+ ErrorPoweredOff,
+ ErrorLENotSupported
};
@interface QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) : NSObject
-{// Protocols are adopted in the mm file.
- QT_PREPEND_NAMESPACE(OSXBluetooth)::LEDeviceInquiryDelegate *delegate;
+{
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<NSMutableSet> uuids;
+ QT_PREPEND_NAMESPACE(OSXBluetooth)::ObjCScopedPointer<CBCentralManager> manager;
- // TODO: scoped pointers/shared pointers?
- NSMutableDictionary *peripherals; // Found devices.
- CBCentralManager *manager;
+ QList<QBluetoothDeviceInfo> devices;
- LEScanPhase scanPhase;
- bool cancelled;
- QTime startTime;
-}
+ LEInquiryState internalState;
+ QT_PREPEND_NAMESPACE(QAtomicInt) state;
-// Inquiry length in milliseconds.
-+ (int)inquiryLength;
+ // Timers to check if we can execute delayed callbacks:
+ QT_PREPEND_NAMESPACE(QElapsedTimer) errorTimer;
+ QT_PREPEND_NAMESPACE(QElapsedTimer) scanTimer;
+}
-- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LEDeviceInquiryDelegate *)aDelegate;
+- (id)init;
- (void)dealloc;
-// Actual scan can be delayed - we have to wait for a status update first.
-- (bool)start;
-// Stop can be delayed - if we're waiting for a status update.
+// IMPORTANT: both 'start' and 'stop' are to be executed on the "Qt's LE queue".
+- (void)start;
- (void)stop;
-- (bool)isActive;
-- (const QTime &)startTime;
+- (LEInquiryState)inquiryState;
+- (const QList<QBluetoothDeviceInfo> &)discoveredDevices;
@end
diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm
index a5d3d936..08ff699d 100644
--- a/src/bluetooth/osx/osxbtutility.mm
+++ b/src/bluetooth/osx/osxbtutility.mm
@@ -305,6 +305,47 @@ ObjCStrongReference<NSData> data_from_bytearray(const QByteArray & qtData)
return result;
}
+// A small RAII class for a dispatch queue.
+class SerialDispatchQueue
+{
+public:
+ explicit SerialDispatchQueue(const char *label)
+ {
+ Q_ASSERT(label);
+
+ queue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
+ if (!queue) {
+ qCCritical(QT_BT_OSX) << "failed to create dispatch queue with label"
+ << label;
+ }
+ }
+ ~SerialDispatchQueue()
+ {
+ if (queue)
+ dispatch_release(queue);
+ }
+
+ dispatch_queue_t data() const
+ {
+ return queue;
+ }
+private:
+ dispatch_queue_t queue;
+
+ Q_DISABLE_COPY(SerialDispatchQueue)
+};
+
+dispatch_queue_t qt_LE_queue()
+{
+ static const SerialDispatchQueue leQueue("qt-bluetooth-LE-queue");
+ return leQueue.data();
+}
+
+unsigned qt_LE_deviceInquiryLength()
+{
+ return 10;
+}
+
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h
index a69e05c2..3506b0d1 100644
--- a/src/bluetooth/osx/osxbtutility_p.h
+++ b/src/bluetooth/osx/osxbtutility_p.h
@@ -306,6 +306,10 @@ inline QSysInfo::MacVersion qt_OS_limit(QSysInfo::MacVersion osxVersion, QSysInf
#endif
}
+dispatch_queue_t qt_LE_queue();
+// LE scan, in seconds.
+unsigned qt_LE_deviceInquiryLength();
+
} // namespace OSXBluetooth
// Logging category for both OS X and iOS.
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
index 1556c5f9..30a6acb6 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
@@ -31,6 +31,7 @@
**
****************************************************************************/
+#include "qbluetoothdevicediscoverytimer_osx_p.h"
#include "qbluetoothdevicediscoveryagent.h"
#include "osx/osxbtledeviceinquiry_p.h"
#include "qbluetoothlocaldevice.h"
@@ -51,27 +52,29 @@ QT_BEGIN_NAMESPACE
using OSXBluetooth::ObjCScopedPointer;
-class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::LEDeviceInquiryDelegate
+class QBluetoothDeviceDiscoveryAgentPrivate
{
friend class QBluetoothDeviceDiscoveryAgent;
+ friend class OSXBluetooth::DDATimerHandler;
+
public:
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &address,
QBluetoothDeviceDiscoveryAgent *q);
virtual ~QBluetoothDeviceDiscoveryAgentPrivate();
- bool isValid() const;
bool isActive() const;
void start();
void stop();
private:
- // LEDeviceInquiryDelegate:
- void LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error) Q_DECL_OVERRIDE;
- void LEnotSupported() Q_DECL_OVERRIDE;
- void LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
- NSDictionary *advertisementData, NSNumber *RSSI) Q_DECL_OVERRIDE;
- void LEdeviceInquiryFinished() Q_DECL_OVERRIDE;
+ typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+
+ void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
+ void LEnotSupported();
+ void LEdeviceFound(const QBluetoothDeviceInfo &info);
+ void LEinquiryFinished();
+ void checkLETimeout();
void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString());
@@ -90,8 +93,45 @@ private:
bool startPending;
bool stopPending;
+
+ QScopedPointer<OSXBluetooth::DDATimerHandler> timer;
};
+namespace OSXBluetooth {
+
+DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
+ : owner(d)
+{
+ Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
+
+ timer.setSingleShot(false);
+ connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
+}
+
+void DDATimerHandler::start(int msec)
+{
+ Q_ASSERT_X(msec > 0, Q_FUNC_INFO, "invalid time interval");
+ if (timer.isActive()) {
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "timer is active";
+ return;
+ }
+
+ timer.start(msec);
+}
+
+void DDATimerHandler::stop()
+{
+ timer.stop();
+}
+
+void DDATimerHandler::onTimer()
+{
+ Q_ASSERT(owner);
+ owner->checkLETimeout();
+}
+
+}
+
QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress &adapter,
QBluetoothDeviceDiscoveryAgent *q) :
q_ptr(q),
@@ -103,29 +143,20 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con
Q_UNUSED(adapter);
Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)");
-
- // OSXBTLEDeviceInquiry can be constructed even if LE is not supported -
- // at this stage it's only a memory allocation of the object itself,
- // if it fails - we have some memory-related problems.
- LEDeviceInquiry newInquiryLE([[LEDeviceInquiryObjC alloc] initWithDelegate:this]);
- if (!newInquiryLE) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to initialize a device inquiry object";
- return;
- }
-
- inquiryLE.reset(newInquiryLE.take());
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
{
-}
-
-bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
-{
- // isValid() - Qt does not use exceptions, but the ctor
- // can fail to initialize some important data-members
- // - this is what meant here by valid/invalid.
- return inquiryLE;
+ if (inquiryLE) {
+ // We want the LE scan to stop as soon as possible.
+ if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
+ // Local variable to be retained ...
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq stop];
+ });
+ }
+ }
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
@@ -135,50 +166,66 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
if (stopPending)
return false;
- return [inquiryLE isActive];
+ return inquiryLE;
}
void QBluetoothDeviceDiscoveryAgentPrivate::start()
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
Q_ASSERT_X(!isActive(), Q_FUNC_INFO, "called on active device discovery agent");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
if (stopPending) {
startPending = true;
return;
}
- discoveredDevices.clear();
- setError(QBluetoothDeviceDiscoveryAgent::NoError);
+ using namespace OSXBluetooth;
- if (![inquiryLE start]) {
- // We can be here only if we have some kind of
- // resource allocation error.
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
+ dispatch_queue_t leQueue(qt_LE_queue());
+ if (!leQueue || !inquiryLE) {
setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED));
emit q_ptr->error(lastError);
}
+
+ discoveredDevices.clear();
+ setError(QBluetoothDeviceDiscoveryAgent::NoError);
+
+ // CoreBluetooth does not have a timeout. We start a timer here
+ // and check if scan really started and if yes if we have a timeout.
+ timer.reset(new OSXBluetooth::DDATimerHandler(this));
+ timer->start(2000);
+
+ // Create a local variable - to have a strong referece in a block.
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq start];
+ });
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent");
Q_ASSERT_X(isActive(), Q_FUNC_INFO, "called whithout active inquiry");
- Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
- Q_FUNC_INFO, "called with invalid bluetooth adapter");
startPending = false;
stopPending = true;
+ dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue());
+ Q_ASSERT(leQueue);
+
setError(QBluetoothDeviceDiscoveryAgent::NoError);
- // Can be asynchronous (depending on a status update of CBCentralManager).
- // The call itself is always 'success'.
- [inquiryLE stop];
+
+ // Create a local variable - to have a strong referece in a block.
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq stop];
+ });
+ // We consider LE scan to be stopped immediately and
+ // do not care about this LEDeviceInquiry object anymore.
+ LEinquiryFinished();
}
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
+void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
{
// At the moment the only error reported by osxbtledeviceinquiry
// can be 'powered off' error, it happens
@@ -187,6 +234,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDevic
Q_ASSERT_X(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError,
Q_FUNC_INFO, "unexpected error");
+ inquiryLE.reset();
+ timer->stop();
+
startPending = false;
stopPending = false;
setError(error);
@@ -195,36 +245,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDevic
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
+ inquiryLE.reset();
+ timer->stop();
+
startPending = false;
stopPending = false;
setError(QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError);
emit q_ptr->error(lastError);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
- NSDictionary *advertisementData,
- NSNumber *RSSI)
+void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(const QBluetoothDeviceInfo &newDeviceInfo)
{
- Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
-
- QT_BT_MAC_AUTORELEASEPOOL;
-
- QString name;
- if (peripheral.name && peripheral.name.length) {
- name = QString::fromNSString(peripheral.name);
- } else {
- NSString *const localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
- if (localName && [localName length])
- name = QString::fromNSString(localName);
- }
-
- // TODO: fix 'classOfDevice' (0 for now).
- QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
- if (RSSI)
- newDeviceInfo.setRssi([RSSI shortValue]);
- // CoreBluetooth scans only for LE devices.
- newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
-
// Update, append or discard.
for (int i = 0, e = discoveredDevices.size(); i < e; ++i) {
if (discoveredDevices[i].deviceUuid() == newDeviceInfo.deviceUuid()) {
@@ -241,9 +272,10 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(CBPeripheral *peripher
emit q_ptr->deviceDiscovered(newDeviceInfo);
}
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryFinished()
+void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
{
- Q_ASSERT_X(isValid(), Q_FUNC_INFO, "invalid device discovery agent");
+ inquiryLE.reset();
+ timer->stop();
if (stopPending && !startPending) {
stopPending = false;
@@ -257,6 +289,41 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryFinished()
}
}
+void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
+{
+ Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
+
+ using namespace OSXBluetooth;
+
+ const LEInquiryState state([inquiryLE inquiryState]);
+ if (state == InquiryStarting || state == InquiryActive)
+ return; // Wait ...
+
+ if (state == ErrorPoweredOff)
+ return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+
+ if (state == ErrorLENotSupported)
+ return LEnotSupported();
+
+ if (state == InquiryFinished) {
+ // Process found devices if any ...
+ const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
+ foreach (const QBluetoothDeviceInfo &info, leDevices) {
+ // We were cancelled on a previous device discovered signal ...
+ if (!inquiryLE)
+ break;
+ LEdeviceFound(info);
+ }
+
+ if (inquiryLE)
+ LEinquiryFinished();
+ return;
+ }
+
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
+ // Actually, this deserves an assert :)
+}
+
void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error,
const QString &text)
{
@@ -329,36 +396,22 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices()
void QBluetoothDeviceDiscoveryAgent::start()
{
if (d_ptr->lastError != InvalidBluetoothAdapterError) {
- if (d_ptr->isValid()) {
- if (!isActive())
- d_ptr->start();
- else
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already started";
- } else {
- // We previously failed to initialize
- // private object correctly.
- d_ptr->setError(InvalidBluetoothAdapterError);
- emit error(InvalidBluetoothAdapterError);
- }
+ if (!isActive())
+ d_ptr->start();
+ else
+ qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "already started";
}
}
void QBluetoothDeviceDiscoveryAgent::stop()
{
- if (d_ptr->isValid()) {
- if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError)
- d_ptr->stop();
- else
- qCDebug(QT_BT_OSX) << Q_FUNC_INFO << "failed to stop";
- }
+ if (isActive() && d_ptr->lastError != InvalidBluetoothAdapterError)
+ d_ptr->stop();
}
bool QBluetoothDeviceDiscoveryAgent::isActive() const
{
- if (d_ptr->isValid())
- return d_ptr->isActive();
-
- return false;
+ return d_ptr->isActive();
}
QBluetoothDeviceDiscoveryAgent::Error QBluetoothDeviceDiscoveryAgent::error() const
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
index ad4183a7..1cfe8286 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm
@@ -59,15 +59,16 @@ QT_BEGIN_NAMESPACE
using OSXBluetooth::ObjCScopedPointer;
-class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate,
- public OSXBluetooth::LEDeviceInquiryDelegate
+class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate
{
friend class QBluetoothDeviceDiscoveryAgent;
friend class OSXBluetooth::DDATimerHandler;
public:
+ typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC;
+
QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address,
QBluetoothDeviceDiscoveryAgent *q);
- virtual ~QBluetoothDeviceDiscoveryAgentPrivate(); // Just to make compiler happy.
+ virtual ~QBluetoothDeviceDiscoveryAgentPrivate();
bool isValid() const;
bool isActive() const;
@@ -87,12 +88,11 @@ private:
void inquiryFinished(IOBluetoothDeviceInquiry *inq) Q_DECL_OVERRIDE;
void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE;
void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE;
- // LEDeviceInquiryDelegate:
- void LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error) Q_DECL_OVERRIDE;
- void LEnotSupported() Q_DECL_OVERRIDE;
- void LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
- NSDictionary *advertisementData, NSNumber *RSSI) Q_DECL_OVERRIDE;
- void LEdeviceInquiryFinished() Q_DECL_OVERRIDE;
+
+ //
+ void LEinquiryFinished();
+ void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error);
+ void LEnotSupported();
// Check if it's a really new device/updated info and emit
// q_ptr->deviceDiscovered.
@@ -138,7 +138,7 @@ DDATimerHandler::DDATimerHandler(QBluetoothDeviceDiscoveryAgentPrivate *d)
{
Q_ASSERT_X(owner, Q_FUNC_INFO, "invalid pointer");
- timer.setSingleShot(true);
+ timer.setSingleShot(false);
connect(&timer, &QTimer::timeout, this, &DDATimerHandler::onTimer);
}
@@ -192,23 +192,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con
return;
}
- // OSXBTLEDeviceInquiry can be constructed even if LE is not supported -
- // at this stage it's only a memory allocation of the object itself,
- // if it fails - we have some memory-related problem.
- LEDeviceInquiry newInquiryLE([[LEDeviceInquiryObjC alloc] initWithDelegate:this]);
- if (!newInquiryLE) {
- qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to "
- "initialize a LE inquiry";
- return;
- }
-
hostController.reset(controller.take());
inquiry.reset(newInquiry.take());
- inquiryLE.reset(newInquiryLE.take());
}
QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
{
+ if (inquiryLE && agentState != NonActive) {
+ // We want the LE scan to stop as soon as possible.
+ if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) {
+ // Local variable to be retained ...
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq stop];
+ });
+ }
+ }
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
@@ -218,7 +217,7 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const
// (and the error is probably not even related to Bluetooth at all)
// - say, allocation error - this is what meant here by valid/invalid.
return hostController && [hostController powerState] == kBluetoothHCIPowerStateON
- && inquiry && inquiryLE;
+ && inquiry;
}
bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
@@ -263,22 +262,29 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE()
Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
Q_FUNC_INFO, "called with an invalid Bluetooth adapter");
- agentState = LEScan;
+ using namespace OSXBluetooth;
- // CoreBluetooth does not have a timeout. We start a timer here
- // and check if scan really started and if yes if we have a timeout.
- timer.reset(new OSXBluetooth::DDATimerHandler(this));
- timer->start([LEDeviceInquiryObjC inquiryLength]);
+ inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]);
- if (![inquiryLE start]) {
- // We can be here only if we have some kind of resource allocation error, so we
- // do not emit finished, we emit error.
- timer->stop();
+ dispatch_queue_t leQueue(qt_LE_queue());
+ if (!leQueue || !inquiryLE) {
setError(QBluetoothDeviceDiscoveryAgent::UnknownError,
QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE));
agentState = NonActive;
emit q_ptr->error(lastError);
}
+
+ agentState = LEScan;
+ // CoreBluetooth does not have a timeout. We start a timer here
+ // and check if scan is active/finished/finished with error(s).
+ timer.reset(new OSXBluetooth::DDATimerHandler(this));
+ timer->start(2000);
+
+ // We need the local variable so that it's retained ...
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq start];
+ });
}
void QBluetoothDeviceDiscoveryAgentPrivate::stop()
@@ -288,6 +294,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError,
Q_FUNC_INFO, "called with invalid bluetooth adapter");
+ using namespace OSXBluetooth;
+
const bool prevStart = startPending;
startPending = false;
stopPending = true;
@@ -304,9 +312,16 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop()
emit q_ptr->error(lastError);
}
} else {
- // Can be asynchronous (depending on a status update of CBCentralManager).
- // The call itself is always 'success'.
- [inquiryLE stop];
+ dispatch_queue_t leQueue(qt_LE_queue());
+ Q_ASSERT(leQueue);
+ // We need the local variable so that it's retained ...
+ LEDeviceInquiryObjC *inq = inquiryLE.data();
+ dispatch_async(leQueue, ^{
+ [inq stop];
+ });
+ // We consider LE scan to be stopped immediately and
+ // do not care about this LEDeviceInquiry object anymore.
+ LEinquiryFinished();
}
}
@@ -430,31 +445,45 @@ void QBluetoothDeviceDiscoveryAgentPrivate::checkLETimeout()
Q_ASSERT_X(agentState == LEScan, Q_FUNC_INFO, "invalid agent state");
Q_ASSERT_X(inquiryLE, Q_FUNC_INFO, "LE device inquiry is nil");
- const int timeout = [LEDeviceInquiryObjC inquiryLength];
- Q_ASSERT(timeout > 0);
- const QTime scanStartTime([inquiryLE startTime]);
- if (scanStartTime.isValid()) {
- const int elapsed = scanStartTime.msecsTo(QTime::currentTime());
- Q_ASSERT(elapsed >= 0);
- if (elapsed >= timeout)
- [inquiryLE stop];
- else
- timer->start(timeout - elapsed);
- } else {
- // Scan not started yet. Wait 5 seconds more.
- timer->start(timeout / 2);
+ using namespace OSXBluetooth;
+
+ const LEInquiryState state([inquiryLE inquiryState]);
+ if (state == InquiryStarting || state == InquiryActive)
+ return; // Wait ...
+
+ if (state == ErrorPoweredOff)
+ return LEinquiryError(QBluetoothDeviceDiscoveryAgent::PoweredOffError);
+
+ if (state == ErrorLENotSupported)
+ return LEnotSupported();
+
+ if (state == InquiryFinished) {
+ // Process found devices if any ...
+ const QList<QBluetoothDeviceInfo> leDevices([inquiryLE discoveredDevices]);
+ foreach (const QBluetoothDeviceInfo &info, leDevices) {
+ // We were cancelled on a previous device discovered signal ...
+ if (agentState != LEScan)
+ break;
+ deviceFound(info);
+ }
+
+ if (agentState == LEScan)
+ LEinquiryFinished();
+ return;
}
+
+ qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "unexpected inquiry state in LE timeout";
+ // Actually, this deserves an assert :)
}
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
+void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error)
{
// At the moment the only error reported can be 'powered off' error, it happens
// after the LE scan started (so we have LE support and this is a real PoweredOffError).
- Q_ASSERT_X(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError,
- Q_FUNC_INFO, "unexpected error code");
+ Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError);
timer->stop();
-
+ inquiryLE.reset();
agentState = NonActive;
setError(error);
emit q_ptr->error(lastError);
@@ -462,45 +491,16 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryError(QBluetoothDevic
void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported()
{
- // Not supported is not an error.
+ // Not supported is not an error (we still have 'Classic').
qCDebug(QT_BT_OSX) << "no Bluetooth LE support";
- // After we call startLE and before receive NotSupported,
- // the user can call stop (setting a pending stop).
- // So the same rule apply:
- timer->stop();
-
- LEdeviceInquiryFinished();
+ LEinquiryFinished();
}
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceFound(CBPeripheral *peripheral, const QBluetoothUuid &deviceUuid,
- NSDictionary *advertisementData,
- NSNumber *RSSI)
-{
- Q_ASSERT_X(peripheral, Q_FUNC_INFO, "invalid peripheral (nil)");
- Q_ASSERT_X(agentState == LEScan, Q_FUNC_INFO,
- "invalid agent state, expected LE scan");
-
- Q_UNUSED(advertisementData)
-
- QString name;
- if (peripheral.name)
- name = QString::fromNSString(peripheral.name);
-
- // TODO: fix 'classOfDevice' (0 for now).
- QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
- if (RSSI)
- newDeviceInfo.setRssi([RSSI shortValue]);
- // CoreBluetooth scans only for LE devices.
- newDeviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
-
- deviceFound(newDeviceInfo);
-}
-
-void QBluetoothDeviceDiscoveryAgentPrivate::LEdeviceInquiryFinished()
+void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished()
{
// The same logic as in inquiryFinished, but does not start LE scan.
agentState = NonActive;
-
+ inquiryLE.reset();
timer->stop();
if (stopPending && !startPending) {