summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
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 /src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm
parentdab0c9a010cf7fcd521707168cc8107b7ed6af24 (diff)
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>
Diffstat (limited to 'src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm')
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_ios.mm231
1 files changed, 142 insertions, 89 deletions
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