diff options
Diffstat (limited to 'src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm')
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm | 316 |
1 files changed, 174 insertions, 142 deletions
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm index 620833de..933049d8 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm @@ -37,21 +37,21 @@ ** ****************************************************************************/ -#include "qbluetoothdevicediscoverytimer_osx_p.h" #include "qbluetoothdevicediscoveryagent.h" #include "osx/osxbtledeviceinquiry_p.h" #include "osx/osxbtdeviceinquiry_p.h" #include "qbluetoothlocaldevice.h" #include "osx/osxbtsdpinquiry_p.h" #include "qbluetoothdeviceinfo.h" +#include "osx/osxbtnotifier_p.h" #include "osx/osxbtutility_p.h" -#include "osx/uistrings_p.h" #include "qbluetoothhostinfo.h" +#include "qbluetoothaddress.h" +#include "osx/uistrings_p.h" #include "qbluetoothuuid.h" #include <QtCore/qloggingcategory.h> #include <QtCore/qscopedpointer.h> -#include <QtCore/qdatetime.h> #include <QtCore/qglobal.h> #include <QtCore/qstring.h> #include <QtCore/qdebug.h> @@ -63,23 +63,40 @@ QT_BEGIN_NAMESPACE -using OSXBluetooth::ObjCScopedPointer; +namespace +{ + +void registerQDeviceDiscoveryMetaType() +{ + static bool initDone = false; + if (!initDone) { + qRegisterMetaType<QBluetoothDeviceInfo>(); + qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); + initDone = true; + } +} + +}//namespace -class QBluetoothDeviceDiscoveryAgentPrivate : public OSXBluetooth::DeviceInquiryDelegate +class QBluetoothDeviceDiscoveryAgentPrivate : public QObject, + public OSXBluetooth::DeviceInquiryDelegate { friend class QBluetoothDeviceDiscoveryAgent; - friend class OSXBluetooth::DDATimerHandler; public: - typedef QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry) LEDeviceInquiryObjC; + template<class T> + using ObjCScopedPointer = OSXBluetooth::ObjCScopedPointer<T>; + using LEDeviceInquiryObjC = QT_MANGLE_NAMESPACE(OSXBTLEDeviceInquiry); QBluetoothDeviceDiscoveryAgentPrivate(const QBluetoothAddress & address, QBluetoothDeviceDiscoveryAgent *q); - virtual ~QBluetoothDeviceDiscoveryAgentPrivate(); + + ~QBluetoothDeviceDiscoveryAgentPrivate() Q_DECL_OVERRIDE; bool isValid() const; bool isActive() const; - void start(); + void start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods); + void startClassic(); void startLE(); void stop(); @@ -95,7 +112,6 @@ private: void error(IOBluetoothDeviceInquiry *inq, IOReturn error) Q_DECL_OVERRIDE; void deviceFound(IOBluetoothDeviceInquiry *inq, IOBluetoothDevice *device) Q_DECL_OVERRIDE; - // void LEinquiryFinished(); void LEinquiryError(QBluetoothDeviceDiscoveryAgent::Error error); void LEnotSupported(); @@ -105,9 +121,8 @@ private: void deviceFound(const QBluetoothDeviceInfo &newDeviceInfo); void setError(IOReturn error, const QString &text = QString()); - void setError(QBluetoothDeviceDiscoveryAgent::Error, const QString &text = QString()); - - void checkLETimeout(); + void setError(QBluetoothDeviceDiscoveryAgent::Error, + const QString &text = QString()); QBluetoothDeviceDiscoveryAgent *q_ptr; AgentState agentState; @@ -122,56 +137,22 @@ private: QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType; - typedef ObjCScopedPointer<DeviceInquiryObjC> DeviceInquiry; + using DeviceInquiry = ObjCScopedPointer<DeviceInquiryObjC>; DeviceInquiry inquiry; - typedef ObjCScopedPointer<LEDeviceInquiryObjC> LEDeviceInquiry; + using LEDeviceInquiry = ObjCScopedPointer<LEDeviceInquiryObjC>; LEDeviceInquiry inquiryLE; - typedef ObjCScopedPointer<IOBluetoothHostController> HostController; + using HostController = ObjCScopedPointer<IOBluetoothHostController>; HostController hostController; - typedef QList<QBluetoothDeviceInfo> DevicesList; + using DevicesList = QList<QBluetoothDeviceInfo>; DevicesList discoveredDevices; - QScopedPointer<OSXBluetooth::DDATimerHandler> timer; + int lowEnergySearchTimeout; + QBluetoothDeviceDiscoveryAgent::DiscoveryMethods requestedMethods; }; -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), @@ -180,26 +161,22 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(con startPending(false), stopPending(false), lastError(QBluetoothDeviceDiscoveryAgent::NoError), - inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry) + inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry), + lowEnergySearchTimeout(OSXBluetooth::defaultLEScanTimeoutMS), + requestedMethods(QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { + registerQDeviceDiscoveryMetaType(); + Q_ASSERT_X(q != Q_NULLPTR, Q_FUNC_INFO, "invalid q_ptr (null)"); HostController controller([[IOBluetoothHostController defaultController] retain]); if (!controller || [controller powerState] != kBluetoothHCIPowerStateON) { - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "no default host " - "controller or adapter is off"; - return; - } - - DeviceInquiry newInquiry([[DeviceInquiryObjC alloc]initWithDelegate:this]); - if (!newInquiry) { // Obj-C's way of "reporting errors": - qCCritical(QT_BT_OSX) << Q_FUNC_INFO << "failed to " - "initialize an inquiry"; + qCCritical(QT_BT_OSX) << "no default host controller or adapter is off"; return; } hostController.reset(controller.take()); - inquiry.reset(newInquiry.take()); } QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() @@ -209,7 +186,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { // Local variable to be retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); } @@ -218,12 +195,7 @@ QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() bool QBluetoothDeviceDiscoveryAgentPrivate::isValid() const { - // isValid() - Qt does not use exceptions, but the ctor - // can fail to initialize some important data-members - // (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; + return hostController && [hostController powerState] == kBluetoothHCIPowerStateON; } bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const @@ -237,23 +209,56 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const return agentState != NonActive; } -void QBluetoothDeviceDiscoveryAgentPrivate::start() +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) { - 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"); + Q_ASSERT(isValid()); + Q_ASSERT(!isActive()); + Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); + Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod + | QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)); + + requestedMethods = methods; if (stopPending) { startPending = true; return; } - agentState = ClassicScan; + // This function (re)starts the scan(s) from the scratch; + // starting from Classic if it's in 'methods' (or LE scan if not). + agentState = NonActive; discoveredDevices.clear(); setError(QBluetoothDeviceDiscoveryAgent::NoError); + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) + return startClassic(); + + startLE(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::startClassic() +{ + Q_ASSERT(isValid()); + Q_ASSERT(!isActive()); + Q_ASSERT(lastError == QBluetoothDeviceDiscoveryAgent::NoError); + Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod); + Q_ASSERT(agentState == NonActive); + + if (!inquiry) { + // The first Classic scan for this DDA. + inquiry.reset([[DeviceInquiryObjC alloc]initWithDelegate:this]); + if (!inquiry) { + qCCritical(QT_BT_OSX) << "failed to initialize an Classic device inquiry"; + setError(QBluetoothDeviceDiscoveryAgent::UnknownError, + QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); + emit q_ptr->error(lastError); + return; + } + } + + agentState = ClassicScan; + const IOReturn res = [inquiry start]; if (res != kIOReturnSuccess) { setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED)); @@ -264,13 +269,29 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() void QBluetoothDeviceDiscoveryAgentPrivate::startLE() { - Q_ASSERT_X(isValid(), Q_FUNC_INFO, "called on invalid device discovery agent"); - Q_ASSERT_X(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError, - Q_FUNC_INFO, "called with an invalid Bluetooth adapter"); + Q_ASSERT(isValid()); + Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); + Q_ASSERT(requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); using namespace OSXBluetooth; - inquiryLE.reset([[LEDeviceInquiryObjC alloc] init]); + QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); + // Connections: + using ErrMemFunPtr = void (LECBManagerNotifier::*)(QBluetoothDeviceDiscoveryAgent::Error); + notifier->connect(notifier.data(), ErrMemFunPtr(&LECBManagerNotifier::CBManagerError), + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError); + notifier->connect(notifier.data(), &LECBManagerNotifier::LEnotSupported, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported); + notifier->connect(notifier.data(), &LECBManagerNotifier::discoveryFinished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished); + using DeviceMemFunPtr = void (QBluetoothDeviceDiscoveryAgentPrivate::*)(const QBluetoothDeviceInfo &); + notifier->connect(notifier.data(), &LECBManagerNotifier::deviceDiscovered, + this, DeviceMemFunPtr(&QBluetoothDeviceDiscoveryAgentPrivate::deviceFound)); + + // Check queue and create scanner: + inquiryLE.reset([[LEDeviceInquiryObjC alloc] initWithNotifier:notifier.data()]); + if (inquiryLE) + notifier.take(); // Whatever happens next, inquiryLE is already the owner ... dispatch_queue_t leQueue(qt_LE_queue()); if (!leQueue || !inquiryLE) { @@ -278,18 +299,15 @@ void QBluetoothDeviceDiscoveryAgentPrivate::startLE() QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STARTED_LE)); agentState = NonActive; emit q_ptr->error(lastError); + return; } + // Now start in on LE queue: 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]; + [inq startWithTimeout:lowEnergySearchTimeout]; }); } @@ -311,7 +329,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() if (agentState == ClassicScan) { const IOReturn res = [inquiry stop]; if (res != kIOReturnSuccess) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "failed to stop"; + qCWarning(QT_BT_OSX) << "failed to stop"; startPending = prevStart; stopPending = false; setError(res, QCoreApplication::translate(DEV_DISCOVERY, DD_NOT_STOPPED)); @@ -322,7 +340,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::stop() Q_ASSERT(leQueue); // We need the local variable so that it's retained ... LEDeviceInquiryObjC *inq = inquiryLE.data(); - dispatch_async(leQueue, ^{ + dispatch_sync(leQueue, ^{ [inq stop]; }); // We consider LE scan to be stopped immediately and @@ -347,13 +365,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::inquiryFinished(IOBluetoothDeviceInq } else if (startPending) { startPending = false; stopPending = false; - start(); + start(requestedMethods); } else { // We can be here _only_ if a classic scan - // finished in a normal way (not cancelled). + // finished in a normal way (not cancelled) + // and requestedMethods includes LowEnergyMethod. // startLE() will take care of old devices // not supporting Bluetooth 4.0. - startLE(); + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + startLE(); + else + emit q_ptr->finished(); } } @@ -385,7 +407,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry // Let's collect some info about this device: const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress])); if (deviceAddress.isNull()) { - qCWarning(QT_BT_OSX) << Q_FUNC_INFO << "invalid Bluetooth address"; + qCWarning(QT_BT_OSX) << "invalid Bluetooth address"; return; } @@ -415,8 +437,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(IOReturn error, const QStri setError(QBluetoothDeviceDiscoveryAgent::UnknownError, text); } -void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, - const QString &text) +void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAgent::Error error, const QString &text) { lastError = error; @@ -443,53 +464,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::setError(QBluetoothDeviceDiscoveryAg } if (lastError != QBluetoothDeviceDiscoveryAgent::NoError) - qCDebug(QT_BT_OSX) << "error set: "<<errorString; -} - -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"); - - 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 :) + qCDebug(QT_BT_OSX) << "error set:"<<errorString; } 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(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError); + Q_ASSERT(error == QBluetoothDeviceDiscoveryAgent::PoweredOffError + || error == QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); - timer->stop(); inquiryLE.reset(); + + startPending = false; + stopPending = false; agentState = NonActive; setError(error); emit q_ptr->error(lastError); @@ -497,9 +483,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryError(QBluetoothDeviceDisco void QBluetoothDeviceDiscoveryAgentPrivate::LEnotSupported() { - // Not supported is not an error (we still have 'Classic'). qCDebug(QT_BT_OSX) << "no Bluetooth LE support"; - LEinquiryFinished(); + + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) { + // Having both Classic | LE means this is not an error. + LEinquiryFinished(); + } else { + // In the past this was never an error, that's why we have + // LEnotSupported as a special method. But now, since + // we can have separate Classic/LE scans, we have to report it + // as UnsupportedDiscoveryMethod. + LEinquiryError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + } } void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() @@ -507,7 +502,6 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() // The same logic as in inquiryFinished, but does not start LE scan. agentState = NonActive; inquiryLE.reset(); - timer->stop(); if (stopPending && !startPending) { stopPending = false; @@ -515,7 +509,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::LEinquiryFinished() } else if (startPending) { startPending = false; stopPending = false; - start(); //Start from a classic scan again. + start(requestedMethods); //Start again. } else { emit q_ptr->finished(); } @@ -575,7 +569,7 @@ QBluetoothDeviceDiscoveryAgent::InquiryType QBluetoothDeviceDiscoveryAgent::inqu return d_ptr->inquiryType; } -void QBluetoothDeviceDiscoveryAgent::setInquiryType(QBluetoothDeviceDiscoveryAgent::InquiryType type) +void QBluetoothDeviceDiscoveryAgent::setInquiryType(InquiryType type) { d_ptr->inquiryType = type; } @@ -585,12 +579,33 @@ QList<QBluetoothDeviceInfo> QBluetoothDeviceDiscoveryAgent::discoveredDevices() return d_ptr->discoveredDevices; } +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return ClassicMethod | LowEnergyMethod; +} + void QBluetoothDeviceDiscoveryAgent::start() { + start(supportedDiscoveryMethods()); +} + +void QBluetoothDeviceDiscoveryAgent::start(DiscoveryMethods methods) +{ + if (methods == NoMethod) + return; + + if ((supportedDiscoveryMethods() & methods) != methods) { + d_ptr->lastError = UnsupportedDiscoveryMethod; + d_ptr->errorString = tr("One or more device discovery methods " + "are not supported on this platform"); + emit error(d_ptr->lastError); + return; + } + if (d_ptr->lastError != InvalidBluetoothAdapterError) { if (d_ptr->isValid()) { if (!isActive()) - d_ptr->start(); + d_ptr->start(methods); } else { // We previously failed to initialize d_ptr correctly: // either some memory allocation problem or @@ -627,4 +642,21 @@ QString QBluetoothDeviceDiscoveryAgent::errorString() const return d_ptr->errorString; } +void QBluetoothDeviceDiscoveryAgent::setLowEnergyDiscoveryTimeout(int timeout) +{ + // cannot deliberately turn it off + if (timeout < 0) { + qCDebug(QT_BT_OSX) << "The Bluetooth Low Energy device discovery timeout cannot be negative."; + return; + } + + d_ptr->lowEnergySearchTimeout = timeout; + return; +} + +int QBluetoothDeviceDiscoveryAgent::lowEnergyDiscoveryTimeout() const +{ + return d_ptr->lowEnergySearchTimeout; +} + QT_END_NAMESPACE |