summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarja Sundqvist <tarja.sundqvist@qt.io>2023-03-23 21:57:01 +0200
committerTarja Sundqvist <tarja.sundqvist@qt.io>2023-03-23 21:57:01 +0200
commit3dcb548bd6fe4aa488ca0bac95e65a7a366c3eca (patch)
treed8e263e0341880d970832326c7c7cecfd05e67cd
parente957d4810b05d8453e163d7bcdcab42d3c60a7bb (diff)
parent2b3b5c899ebdb0fc11273af7370804f5f03ad9e8 (diff)
Merge remote-tracking branch 'origin/tqtc/lts-5.15.9' into tqtc/lts-5.15-opensourcev5.15.9-lts-lgpl
-rw-r--r--.qmake.conf2
-rw-r--r--examples/bluetooth/pingpong/pingpong.cpp13
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java1044
-rw-r--r--src/bluetooth/bluez/hcimanager.cpp6
-rw-r--r--src/bluetooth/osx/osxbtconnectionmonitor.mm9
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm19
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm160
-rw-r--r--src/bluetooth/osx/osxbtservicerecord.mm50
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp32
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp5
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp11
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm25
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_p.h2
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp75
-rw-r--r--src/bluetooth/qbluetoothsocket.cpp7
-rw-r--r--src/bluetooth/qbluetoothsocket_bluezdbus.cpp4
-rw-r--r--src/bluetooth/qbluetoothsocket_winrt.cpp43
-rw-r--r--src/bluetooth/qlowenergycontroller_winrt_new.cpp17
-rw-r--r--src/bluetooth/qlowenergyserviceprivate_p.h1
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro2
-rw-r--r--tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp4
-rw-r--r--tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro2
-rw-r--r--tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp23
-rw-r--r--tests/auto/qbluetoothserviceinfo/tst_qbluetoothserviceinfo.cpp20
-rw-r--r--tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp12
-rw-r--r--tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro2
-rw-r--r--tests/auto/qlowenergycontroller/qlowenergycontroller.pro2
-rw-r--r--tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp3
-rw-r--r--tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro2
-rw-r--r--tests/auto/shared/Info.macos.plist24
30 files changed, 993 insertions, 628 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 48f4c9ef..213056f5 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -2,4 +2,4 @@ load(qt_build_config)
DEFINES += QT_NO_FOREACH QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST
-MODULE_VERSION = 5.15.8
+MODULE_VERSION = 5.15.9
diff --git a/examples/bluetooth/pingpong/pingpong.cpp b/examples/bluetooth/pingpong/pingpong.cpp
index d80df3d6..aaa625f3 100644
--- a/examples/bluetooth/pingpong/pingpong.cpp
+++ b/examples/bluetooth/pingpong/pingpong.cpp
@@ -64,8 +64,7 @@ PingPong::PingPong():
PingPong::~PingPong()
{
- delete m_timer;
- delete m_serverInfo;
+ m_timer->stop();
delete socket;
delete discoveryAgent;
}
@@ -249,7 +248,7 @@ void PingPong::startServer()
this, &PingPong::serverError);
const QBluetoothUuid uuid(serviceUuid);
- m_serverInfo->listen(uuid, QStringLiteral("PingPong server"));
+ m_serviceInfo = m_serverInfo->listen(uuid, QStringLiteral("PingPong server"));
//! [Starting the server]
setMessage(QStringLiteral("Server started, waiting for the client. You are the left player."));
// m_role is set to 1 if it is a server
@@ -344,7 +343,12 @@ void PingPong::done()
void PingPong::addService(const QBluetoothServiceInfo &service)
{
- setMessage("Service found. Setting parameters...");
+ if (m_serviceFound)
+ return;
+ m_serviceFound = true;
+
+ setMessage("Service found. Stopping discovery and creating connection...");
+ discoveryAgent->stop();
//! [Connecting the socket]
socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
socket->connectToService(service);
@@ -353,7 +357,6 @@ void PingPong::addService(const QBluetoothServiceInfo &service)
connect(socket, &QBluetoothSocket::connected, this, &PingPong::serverConnected);
connect(socket, &QBluetoothSocket::disconnected, this, &PingPong::serverDisconnected);
//! [Connecting the socket]
- m_serviceFound = true;
}
void PingPong::serviceScanError(QBluetoothServiceDiscoveryAgent::Error error)
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
index 96243af8..5fc3ef41 100644
--- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
+++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
@@ -128,6 +128,12 @@ public class QtBluetoothLE {
private int pendingJobHandle = -1;
};
+ // The handleOn* functions in this class are callback handlers which are synchronized
+ // to "this" client object. This protects the member variables which could be
+ // concurrently accessed from Qt (JNI) thread and different Java threads *)
+ // *) The newer Android API (starting Android 8.1) synchronizes callbacks to one
+ // Java thread, but this is not true for the earlier API which we still support.
+ //
// In case bond state has been changed due to access to a restricted handle,
// Android never completes the operation which triggered the devices to bind
// and thus never fires on(Characteristic|Descriptor)(Read|Write) callback,
@@ -136,50 +142,54 @@ public class QtBluetoothLE {
// re-add the currently pending job to the queue's head and re-run it.
// If, by some reason, bonding process has been interrupted, either
// re-add the currently pending job to the queue's head and re-run it.
- private class BondStateBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mBluetoothGatt == null)
- return;
+ private synchronized void handleOnReceive(Context context, Intent intent)
+ {
+ if (mBluetoothGatt == null)
+ return;
- final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
- return;
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
- final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
- final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState =
+ intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
- if (bondState == BluetoothDevice.BOND_BONDING) {
- synchronized (readWriteQueue) {
- if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
- return;
- }
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
+ return;
- timeoutHandler.removeCallbacksAndMessages(null);
- handleForTimeout.set(HANDLE_FOR_RESET);
- } else if (previousBondState == BluetoothDevice.BOND_BONDING && (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE)) {
- synchronized (readWriteQueue) {
- if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
- return;
+ timeoutHandler.removeCallbacksAndMessages(null);
+ handleForTimeout.set(HANDLE_FOR_RESET);
+ } else if (previousBondState == BluetoothDevice.BOND_BONDING &&
+ (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE)) {
+ if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
+ return;
- readWriteQueue.addFirst(pendingJob);
- pendingJob = null;
- }
+ readWriteQueue.addFirst(pendingJob);
+ pendingJob = null;
- performNextIO();
- } else if (previousBondState == BluetoothDevice.BOND_BONDED && bondState == BluetoothDevice.BOND_NONE) {
- // peripheral or central removed the bond information;
- // if it was peripheral, the connection attempt would fail with PIN_OR_KEY_MISSING,
- // which is handled by Android by broadcasting ACTION_BOND_STATE_CHANGED
- // with new state BOND_NONE, without actually deleting the bond information :facepalm:
- // if we get there, it is safer to delete it now, by invoking the undocumented API call
- try {
- device.getClass().getMethod("removeBond").invoke(device);
- } catch (Exception ex) {
- ex.printStackTrace();
- }
+ performNextIO();
+ } else if (previousBondState == BluetoothDevice.BOND_BONDED
+ && bondState == BluetoothDevice.BOND_NONE) {
+ // peripheral or central removed the bond information;
+ // if it was peripheral, the connection attempt would fail with PIN_OR_KEY_MISSING,
+ // which is handled by Android by broadcasting ACTION_BOND_STATE_CHANGED
+ // with new state BOND_NONE, without actually deleting the bond information :facepalm:
+ // if we get there, it is safer to delete it now, by invoking the undocumented API call
+ try {
+ device.getClass().getMethod("removeBond").invoke(device);
+ } catch (Exception ex) {
+ ex.printStackTrace();
}
}
+ }
+
+ private class BondStateBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleOnReceive(context, intent);
+ }
};
private BroadcastReceiver bondStateBroadcastReceiver = null;
@@ -203,11 +213,11 @@ public class QtBluetoothLE {
/*************************************************************/
/* Device scan */
+ /* Returns true, if request was successfully completed */
+ /* This function is called from Qt thread, but only accesses */
+ /* variables that are not accessed from Java threads */
/*************************************************************/
- /*
- Returns true, if request was successfully completed
- */
public boolean scanForLeDevice(final boolean isEnabled) {
if (isEnabled == mLeScanRunning)
return true;
@@ -261,343 +271,419 @@ public class QtBluetoothLE {
public native void leScanResult(long qtObject, BluetoothDevice device, int rssi, byte[] scanRecord);
- /*************************************************************/
- /* Service Discovery */
- /*************************************************************/
-
- private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
-
- public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
- if (qtObject == 0)
- return;
+ private synchronized void handleOnConnectionStateChange(BluetoothGatt gatt,
+ int status, int newState) {
+ if (qtObject == 0)
+ return;
- int qLowEnergyController_State = 0;
- //This must be in sync with QLowEnergyController::ControllerState
- switch (newState) {
- case BluetoothProfile.STATE_DISCONNECTED:
- if (bondStateBroadcastReceiver != null) {
- qtContext.unregisterReceiver(bondStateBroadcastReceiver);
- bondStateBroadcastReceiver = null;
- }
+ int qLowEnergyController_State = 0;
+ //This must be in sync with QLowEnergyController::ControllerState
+ switch (newState) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ if (bondStateBroadcastReceiver != null) {
+ qtContext.unregisterReceiver(bondStateBroadcastReceiver);
+ bondStateBroadcastReceiver = null;
+ }
- qLowEnergyController_State = 0;
- // we disconnected -> get rid of data from previous run
- resetData();
- // reset mBluetoothGatt, reusing same object is not very reliable
- // sometimes it reconnects and sometimes it does not.
- if (mBluetoothGatt != null) {
- mBluetoothGatt.close();
- if (mHandler != null) {
- mHandler.getLooper().quitSafely();
- mHandler = null;
- }
+ qLowEnergyController_State = 0;
+ // we disconnected -> get rid of data from previous run
+ resetData();
+ // reset mBluetoothGatt, reusing same object is not very reliable
+ // sometimes it reconnects and sometimes it does not.
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ if (mHandler != null) {
+ mHandler.getLooper().quitSafely();
+ mHandler = null;
}
- mBluetoothGatt = null;
- break;
- case BluetoothProfile.STATE_CONNECTED:
- if (bondStateBroadcastReceiver == null) {
- bondStateBroadcastReceiver = new BondStateBroadcastReceiver();
- qtContext.registerReceiver(bondStateBroadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
- }
- qLowEnergyController_State = 2;
- }
-
- //This must be in sync with QLowEnergyController::Error
- int errorCode;
- switch (status) {
- case BluetoothGatt.GATT_SUCCESS:
- errorCode = 0; break; //QLowEnergyController::NoError
- case BluetoothGatt.GATT_FAILURE: // Android's equivalent of "do not know what error it is"
- errorCode = 1; break; //QLowEnergyController::UnknownError
- case 8: // BLE_HCI_CONNECTION_TIMEOUT
- Log.w(TAG, "Connection Error: Try to delay connect() call after previous activity");
- errorCode = 5; break; //QLowEnergyController::ConnectionError
- case 19: // BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION
- case 20: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES
- case 21: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF
- Log.w(TAG, "The remote host closed the connection");
- errorCode = 7; //QLowEnergyController::RemoteHostClosedError
- break;
- case 22: // BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION
- // Internally, Android maps PIN_OR_KEY_MISSING to GATT_CONN_TERMINATE_LOCAL_HOST
- errorCode = 8; break; //QLowEnergyController::AuthorizationError
- default:
- Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status + " " + newState);
- errorCode = status; break; //TODO deal with all errors
- }
- leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State);
+ }
+ mBluetoothGatt = null;
+ break;
+ case BluetoothProfile.STATE_CONNECTED:
+ if (bondStateBroadcastReceiver == null) {
+ bondStateBroadcastReceiver = new BondStateBroadcastReceiver();
+ qtContext.registerReceiver(bondStateBroadcastReceiver,
+ new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+ }
+ qLowEnergyController_State = 2;
}
- public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- //This must be in sync with QLowEnergyController::Error
- int errorCode;
- StringBuilder builder = new StringBuilder();
- switch (status) {
- case BluetoothGatt.GATT_SUCCESS:
- errorCode = 0; //QLowEnergyController::NoError
- final List<BluetoothGattService> services = mBluetoothGatt.getServices();
- for (BluetoothGattService service: services) {
- builder.append(service.getUuid().toString()).append(" "); //space is separator
- }
- break;
- default:
- Log.w(TAG, "Unhandled error code on onServicesDiscovered: " + status);
- errorCode = status; break; //TODO deal with all errors
- }
- leServicesDiscovered(qtObject, errorCode, builder.toString());
+ //This must be in sync with QLowEnergyController::Error
+ int errorCode;
+ switch (status) {
+ case BluetoothGatt.GATT_SUCCESS:
+ errorCode = 0; //QLowEnergyController::NoError
+ break;
+ case BluetoothGatt.GATT_FAILURE: // Android's equivalent of "do not know what error"
+ errorCode = 1; //QLowEnergyController::UnknownError
+ break;
+ case 8: // BLE_HCI_CONNECTION_TIMEOUT
+ Log.w(TAG, "Connection Error: Try to delay connect() call after previous activity");
+ errorCode = 5; //QLowEnergyController::ConnectionError
+ break;
+ case 19: // BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION
+ case 20: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES
+ case 21: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF
+ Log.w(TAG, "The remote host closed the connection");
+ errorCode = 7; //QLowEnergyController::RemoteHostClosedError
+ break;
+ case 22: // BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION
+ // Internally, Android maps PIN_OR_KEY_MISSING to GATT_CONN_TERMINATE_LOCAL_HOST
+ errorCode = 8; //QLowEnergyController::AuthorizationError
+ break;
+ default:
+ Log.w(TAG, "Unhandled error code on connectionStateChanged: "
+ + status + " " + newState);
+ errorCode = status;
+ break; //TODO deal with all errors
+ }
+ leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State);
+ }
- scheduleMtuExchange();
+ private synchronized void handleOnServicesDiscovered(BluetoothGatt gatt, int status) {
+ //This must be in sync with QLowEnergyController::Error
+ int errorCode;
+ StringBuilder builder = new StringBuilder();
+ switch (status) {
+ case BluetoothGatt.GATT_SUCCESS:
+ errorCode = 0; //QLowEnergyController::NoError
+ final List<BluetoothGattService> services = mBluetoothGatt.getServices();
+ for (BluetoothGattService service: services) {
+ builder.append(service.getUuid().toString()).append(" "); //space is separator
+ }
+ break;
+ default:
+ Log.w(TAG, "Unhandled error code on onServicesDiscovered: " + status);
+ errorCode = status; break; //TODO deal with all errors
}
+ leServicesDiscovered(qtObject, errorCode, builder.toString());
+ if (status == BluetoothGatt.GATT_SUCCESS)
+ scheduleMtuExchange();
+ }
- public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
- android.bluetooth.BluetoothGattCharacteristic characteristic,
- int status)
- {
- int foundHandle = -1;
- synchronized (this) {
- foundHandle = handleForCharacteristic(characteristic);
- if (foundHandle == -1 || foundHandle >= entries.size() ) {
- Log.w(TAG, "Cannot find characteristic read request for read notification - handle: " +
- foundHandle + " size: " + entries.size());
-
- //unlock the queue for next item
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
+ private synchronized void handleOnCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status)
+ {
+ int foundHandle = handleForCharacteristic(characteristic);
+ if (foundHandle == -1 || foundHandle >= entries.size() ) {
+ Log.w(TAG, "Cannot find characteristic read request for read notification - handle: " +
+ foundHandle + " size: " + entries.size());
- performNextIO();
- return;
- }
- }
+ //unlock the queue for next item
+ pendingJob = null;
- boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET);
- if (requestTimedOut) {
- Log.w(TAG, "Late char read reply after timeout was hit for handle " + foundHandle);
- // Timeout has hit before this response -> ignore the response
- // no need to unlock pendingJob -> the timeout has done that already
- return;
- }
+ performNextIO();
+ return;
+ }
- GattEntry entry = entries.get(foundHandle);
- final boolean isServiceDiscoveryRun = !entry.valueKnown;
- entry.valueKnown = true;
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read),
+ HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late char read reply after timeout was hit for handle " + foundHandle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock pendingJob -> the timeout has done that already
+ return;
+ }
- if (status == BluetoothGatt.GATT_SUCCESS) {
- // Qt manages handles starting at 1, in Java we use a system starting with 0
- //TODO avoid sending service uuid -> service handle should be sufficient
+ GattEntry entry = entries.get(foundHandle);
+ final boolean isServiceDiscoveryRun = !entry.valueKnown;
+ entry.valueKnown = true;
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ // Qt manages handles starting at 1, in Java we use a system starting with 0
+ //TODO avoid sending service uuid -> service handle should be sufficient
+ leCharacteristicRead(qtObject,
+ characteristic.getService().getUuid().toString(),
+ foundHandle + 1, characteristic.getUuid().toString(),
+ characteristic.getProperties(), characteristic.getValue());
+ } else {
+ if (isServiceDiscoveryRun) {
+ Log.w(TAG, "onCharacteristicRead during discovery error: " + status);
+
+ Log.d(TAG, "Non-readable characteristic " + characteristic.getUuid() +
+ " for service " + characteristic.getService().getUuid());
leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
- foundHandle + 1, characteristic.getUuid().toString(),
- characteristic.getProperties(), characteristic.getValue());
+ foundHandle + 1, characteristic.getUuid().toString(),
+ characteristic.getProperties(), characteristic.getValue());
} else {
- if (isServiceDiscoveryRun) {
- Log.w(TAG, "onCharacteristicRead during discovery error: " + status);
-
- Log.d(TAG, "Non-readable characteristic " + characteristic.getUuid() +
- " for service " + characteristic.getService().getUuid());
- leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
- foundHandle + 1, characteristic.getUuid().toString(),
- characteristic.getProperties(), characteristic.getValue());
- } else {
- // This must be in sync with QLowEnergyService::CharacteristicReadError
- final int characteristicReadError = 5;
- leServiceError(qtObject, foundHandle + 1, characteristicReadError);
- }
+ // This must be in sync with QLowEnergyService::CharacteristicReadError
+ final int characteristicReadError = 5;
+ leServiceError(qtObject, foundHandle + 1, characteristicReadError);
}
+ }
- if (isServiceDiscoveryRun) {
+ if (isServiceDiscoveryRun) {
- // last entry of pending service discovery run -> send discovery finished state update
- GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
- if (serviceEntry.endHandle == foundHandle)
- finishCurrentServiceDiscovery(entry.associatedServiceHandle);
- }
+ // last entry of pending service discovery run -> send discovery finished state update
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == foundHandle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
+ }
+
+ //unlock the queue for next item
+ pendingJob = null;
+
+ performNextIO();
+ }
+
+ private synchronized void handleOnCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic)
+ {
+ int handle = handleForCharacteristic(characteristic);
+ if (handle == -1) {
+ Log.w(TAG,"onCharacteristicChanged: cannot find handle");
+ return;
+ }
+
+ leCharacteristicChanged(qtObject, handle+1, characteristic.getValue());
+ }
+
+ private synchronized void handleOnCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic,
+ int status)
+ {
+ if (status != BluetoothGatt.GATT_SUCCESS)
+ Log.w(TAG, "onCharacteristicWrite: error " + status);
+
+ int handle = handleForCharacteristic(characteristic);
+ if (handle == -1) {
+ Log.w(TAG,"onCharacteristicWrite: cannot find handle");
+ return;
+ }
+
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(handle, IoJobType.Write),
+ HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late char write reply after timeout was hit for handle " + handle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock pendingJob -> the timeout has done that already
+ return;
+ }
+
+ int errorCode;
+ //This must be in sync with QLowEnergyService::ServiceError
+ switch (status) {
+ case BluetoothGatt.GATT_SUCCESS:
+ errorCode = 0;
+ break; // NoError
+ default:
+ errorCode = 2;
+ break; // CharacteristicWriteError
+ }
+
+ byte[] value;
+ value = pendingJob.newValue;
+ pendingJob = null;
+
+ leCharacteristicWritten(qtObject, handle+1, value, errorCode);
+ performNextIO();
+ }
+
+ private synchronized void handleOnDescriptorRead(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ int foundHandle = handleForDescriptor(descriptor);
+ if (foundHandle == -1 || foundHandle >= entries.size() ) {
+ Log.w(TAG, "Cannot find descriptor read request for read notification - handle: " +
+ foundHandle + " size: " + entries.size());
//unlock the queue for next item
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
+ pendingJob = null;
performNextIO();
+ return;
}
- public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
- android.bluetooth.BluetoothGattCharacteristic characteristic,
- int status)
- {
- if (status != BluetoothGatt.GATT_SUCCESS)
- Log.w(TAG, "onCharacteristicWrite: error " + status);
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read),
+ HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late descriptor read reply after timeout was hit for handle " +
+ foundHandle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock pendingJob -> the timeout has done that already
+ return;
+ }
- int handle = handleForCharacteristic(characteristic);
- if (handle == -1) {
- Log.w(TAG,"onCharacteristicWrite: cannot find handle");
- return;
- }
+ GattEntry entry = entries.get(foundHandle);
+ final boolean isServiceDiscoveryRun = !entry.valueKnown;
+ entry.valueKnown = true;
- boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET);
- if (requestTimedOut) {
- Log.w(TAG, "Late char write reply after timeout was hit for handle " + handle);
- // Timeout has hit before this response -> ignore the response
- // no need to unlock pendingJob -> the timeout has done that already
- return;
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ //TODO avoid sending service and characteristic uuid -> handles should be sufficient
+ leDescriptorRead(qtObject,
+ descriptor.getCharacteristic().getService().getUuid().toString(),
+ descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
+ descriptor.getUuid().toString(), descriptor.getValue());
+ } else {
+ if (isServiceDiscoveryRun) {
+ // Cannot read but still advertise the fact that we found a descriptor
+ // The value will be empty.
+ Log.w(TAG, "onDescriptorRead during discovery error: " + status);
+ Log.d(TAG, "Non-readable descriptor " + descriptor.getUuid() +
+ " for characteristic " + descriptor.getCharacteristic().getUuid() +
+ " for service " + descriptor.getCharacteristic().getService().getUuid());
+ leDescriptorRead(qtObject,
+ descriptor.getCharacteristic().getService().getUuid().toString(),
+ descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
+ descriptor.getUuid().toString(), descriptor.getValue());
+ } else {
+ // This must be in sync with QLowEnergyService::DescriptorReadError
+ final int descriptorReadError = 6;
+ leServiceError(qtObject, foundHandle + 1, descriptorReadError);
}
- int errorCode;
- //This must be in sync with QLowEnergyService::ServiceError
- switch (status) {
- case BluetoothGatt.GATT_SUCCESS:
- errorCode = 0; break; // NoError
- default:
- errorCode = 2; break; // CharacteristicWriteError
+ }
+
+ if (isServiceDiscoveryRun) {
+ // last entry of pending service discovery run? ->send discovery finished state update
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == foundHandle) {
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
}
- byte[] value;
- synchronized (readWriteQueue) {
- value = pendingJob.newValue;
- pendingJob = null;
+ /* Some devices preset ClientCharacteristicConfiguration descriptors
+ * to enable notifications out of the box. However the additional
+ * BluetoothGatt.setCharacteristicNotification call prevents
+ * automatic notifications from coming through. Hence we manually set them
+ * up here.
+ */
+ if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
+ byte[] bytearray = descriptor.getValue();
+ final int value = (bytearray != null && bytearray.length > 0) ? bytearray[0] : 0;
+ // notification or indication bit set?
+ if ((value & 0x03) > 0) {
+ Log.d(TAG, "Found descriptor with automatic notifications.");
+ mBluetoothGatt.setCharacteristicNotification(
+ descriptor.getCharacteristic(), true);
+ }
}
- leCharacteristicWritten(qtObject, handle+1, value, errorCode);
- performNextIO();
}
- public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
- android.bluetooth.BluetoothGattCharacteristic characteristic)
- {
- int handle = handleForCharacteristic(characteristic);
- if (handle == -1) {
- Log.w(TAG,"onCharacteristicChanged: cannot find handle");
- return;
- }
+ //unlock the queue for next item
+ pendingJob = null;
+
+ performNextIO();
+ }
- leCharacteristicChanged(qtObject, handle+1, characteristic.getValue());
+ private synchronized void handleOnDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ if (status != BluetoothGatt.GATT_SUCCESS)
+ Log.w(TAG, "onDescriptorWrite: error " + status);
+
+ int handle = handleForDescriptor(descriptor);
+
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(handle, IoJobType.Write),
+ HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late descriptor write reply after timeout was hit for handle " +
+ handle);
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock pendingJob -> the timeout has done that already
+ return;
}
- public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
- android.bluetooth.BluetoothGattDescriptor descriptor,
- int status)
- {
- int foundHandle = -1;
- synchronized (this) {
- foundHandle = handleForDescriptor(descriptor);
- if (foundHandle == -1 || foundHandle >= entries.size() ) {
- Log.w(TAG, "Cannot find descriptor read request for read notification - handle: " +
- foundHandle + " size: " + entries.size());
-
- //unlock the queue for next item
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
- performNextIO();
- return;
- }
- }
+ int errorCode;
+ //This must be in sync with QLowEnergyService::ServiceError
+ switch (status) {
+ case BluetoothGatt.GATT_SUCCESS:
+ errorCode = 0; break; // NoError
+ default:
+ errorCode = 3; break; // DescriptorWriteError
+ }
- boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET);
- if (requestTimedOut) {
- Log.w(TAG, "Late descriptor read reply after timeout was hit for handle " +
- foundHandle);
- // Timeout has hit before this response -> ignore the response
- // no need to unlock pendingJob -> the timeout has done that already
- return;
- }
+ pendingJob = null;
- GattEntry entry = entries.get(foundHandle);
- final boolean isServiceDiscoveryRun = !entry.valueKnown;
- entry.valueKnown = true;
+ leDescriptorWritten(qtObject, handle+1, descriptor.getValue(), errorCode);
+ performNextIO();
+ }
- if (status == BluetoothGatt.GATT_SUCCESS) {
- //TODO avoid sending service and characteristic uuid -> handles should be sufficient
- leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(),
- descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
- descriptor.getUuid().toString(), descriptor.getValue());
- } else {
- if (isServiceDiscoveryRun) {
- // Cannot read but still advertise the fact that we found a descriptor
- // The value will be empty.
- Log.w(TAG, "onDescriptorRead during discovery error: " + status);
- Log.d(TAG, "Non-readable descriptor " + descriptor.getUuid() +
- " for characteristic " + descriptor.getCharacteristic().getUuid() +
- " for service " + descriptor.getCharacteristic().getService().getUuid());
- leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(),
- descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
- descriptor.getUuid().toString(), descriptor.getValue());
- } else {
- // This must be in sync with QLowEnergyService::DescriptorReadError
- final int descriptorReadError = 6;
- leServiceError(qtObject, foundHandle + 1, descriptorReadError);
- }
+ private synchronized void handleOnMtuChanged(android.bluetooth.BluetoothGatt gatt,
+ int mtu, int status)
+ {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Log.w(TAG, "MTU changed to " + mtu);
+ mSupportedMtu = mtu;
+ } else {
+ Log.w(TAG, "MTU change error " + status + ". New MTU " + mtu);
+ mSupportedMtu = DEFAULT_MTU;
+ }
- }
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late mtu reply after timeout was hit");
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock pendingJob -> the timeout has done that already
+ return;
+ }
- if (isServiceDiscoveryRun) {
- // last entry of pending service discovery run? ->send discovery finished state update
- GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
- if (serviceEntry.endHandle == foundHandle) {
- finishCurrentServiceDiscovery(entry.associatedServiceHandle);
- }
+ pendingJob = null;
- /* Some devices preset ClientCharacteristicConfiguration descriptors
- * to enable notifications out of the box. However the additional
- * BluetoothGatt.setCharacteristicNotification call prevents
- * automatic notifications from coming through. Hence we manually set them
- * up here.
- */
- if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
- byte[] bytearray = descriptor.getValue();
- final int value = (bytearray != null && bytearray.length > 0) ? bytearray[0] : 0;
- // notification or indication bit set?
- if ((value & 0x03) > 0) {
- Log.d(TAG, "Found descriptor with automatic notifications.");
- mBluetoothGatt.setCharacteristicNotification(
- descriptor.getCharacteristic(), true);
- }
- }
- }
+ performNextIO();
+ }
- //unlock the queue for next item
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
+ /*************************************************************/
+ /* Service Discovery */
+ /*************************************************************/
- performNextIO();
+ private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
+
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ super.onConnectionStateChange(gatt, status, newState);
+ handleOnConnectionStateChange(gatt, status, newState);
}
- public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
- android.bluetooth.BluetoothGattDescriptor descriptor,
- int status)
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ handleOnServicesDiscovered(gatt, status);
+
+ }
+
+ public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic,
+ int status)
{
- if (status != BluetoothGatt.GATT_SUCCESS)
- Log.w(TAG, "onDescriptorWrite: error " + status);
-
- int handle = handleForDescriptor(descriptor);
-
- boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET);
- if (requestTimedOut) {
- Log.w(TAG, "Late descriptor write reply after timeout was hit for handle " +
- handle);
- // Timeout has hit before this response -> ignore the response
- // no need to unlock pendingJob -> the timeout has done that already
- return;
- }
+ super.onCharacteristicRead(gatt, characteristic, status);
+ handleOnCharacteristicRead(gatt, characteristic, status);
+ }
- int errorCode;
- //This must be in sync with QLowEnergyService::ServiceError
- switch (status) {
- case BluetoothGatt.GATT_SUCCESS:
- errorCode = 0; break; // NoError
- default:
- errorCode = 3; break; // DescriptorWriteError
- }
+ public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic,
+ int status)
+ {
+ super.onCharacteristicWrite(gatt, characteristic, status);
+ handleOnCharacteristicWrite(gatt, characteristic, status);
+ }
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
+ public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic)
+ {
+ super.onCharacteristicChanged(gatt, characteristic);
+ handleOnCharacteristicChanged(gatt, characteristic);
+ }
- leDescriptorWritten(qtObject, handle+1, descriptor.getValue(), errorCode);
- performNextIO();
+ public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ super.onDescriptorRead(gatt, descriptor, status);
+ handleOnDescriptorRead(gatt, descriptor, status);
+ }
+
+ public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ super.onDescriptorWrite(gatt, descriptor, status);
+ handleOnDescriptorWrite(gatt, descriptor, status);
}
//TODO Requires Android API 21 which is not available on CI yet.
// public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt,
@@ -613,33 +699,13 @@ public class QtBluetoothLE {
// requires Android API v21
public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status)
{
- if (status == BluetoothGatt.GATT_SUCCESS) {
- Log.w(TAG, "MTU changed to " + mtu);
- mSupportedMtu = mtu;
- } else {
- Log.w(TAG, "MTU change error " + status + ". New MTU " + mtu);
- mSupportedMtu = DEFAULT_MTU;
- }
-
- boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET);
- if (requestTimedOut) {
- Log.w(TAG, "Late mtu reply after timeout was hit");
- // Timeout has hit before this response -> ignore the response
- // no need to unlock pendingJob -> the timeout has done that already
- return;
- }
-
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
-
- performNextIO();
+ super.onMtuChanged(gatt, mtu, status);
+ handleOnMtuChanged(gatt, mtu, status);
}
};
-
- public boolean connect() {
+ // This function is called from Qt thread
+ public synchronized boolean connect() {
BluetoothDevice mRemoteGattDevice;
try {
@@ -734,14 +800,16 @@ public class QtBluetoothLE {
return mBluetoothGatt != null;
}
- public void disconnect() {
+ // This function is called from Qt thread
+ public synchronized void disconnect() {
if (mBluetoothGatt == null)
return;
mBluetoothGatt.disconnect();
}
- public boolean discoverServices()
+ // This function is called from Qt thread
+ public synchronized boolean discoverServices()
{
return mBluetoothGatt != null && mBluetoothGatt.discoverServices();
}
@@ -786,7 +854,6 @@ public class QtBluetoothLE {
// index into array is equivalent to handle id
private final ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100);
//backlog of to be discovered services
- // TODO remove
private final LinkedList<Integer> servicesToBeDiscovered = new LinkedList<Integer>();
@@ -875,6 +942,7 @@ public class QtBluetoothLE {
return -1;
}
+ // This function is called from Qt thread (indirectly)
private void populateHandles()
{
// We introduce the notion of artificial handles. While GATT handles
@@ -938,21 +1006,18 @@ public class QtBluetoothLE {
private void resetData()
{
- synchronized (this) {
- uuidToEntry.clear();
- entries.clear();
- servicesToBeDiscovered.clear();
- }
+ uuidToEntry.clear();
+ entries.clear();
+ servicesToBeDiscovered.clear();
// kill all timeout handlers
timeoutHandler.removeCallbacksAndMessages(null);
handleForTimeout.set(HANDLE_FOR_RESET);
- synchronized (readWriteQueue) {
- readWriteQueue.clear();
- }
+ readWriteQueue.clear();
}
+ // This function is called from Qt thread
public synchronized boolean discoverServiceDetails(String serviceUuid)
{
try {
@@ -1009,9 +1074,9 @@ public class QtBluetoothLE {
/*
Returns the uuids of the services included by the given service. Otherwise returns null.
- Directly called from Qt.
+ This function is called from Qt thread
*/
- public String includedServices(String serviceUuid)
+ public synchronized String includedServices(String serviceUuid)
{
if (mBluetoothGatt == null)
return null;
@@ -1041,24 +1106,22 @@ public class QtBluetoothLE {
return builder.toString();
}
- //TODO function not yet used
- private void finishCurrentServiceDiscovery(int handleDiscoveredService)
+ private synchronized void finishCurrentServiceDiscovery(int handleDiscoveredService)
{
Log.w(TAG, "Finished current discovery for service handle " + handleDiscoveredService);
GattEntry discoveredService = entries.get(handleDiscoveredService);
discoveredService.valueKnown = true;
- synchronized (this) {
- try {
- servicesToBeDiscovered.removeFirst();
- } catch (NoSuchElementException ex) {
- Log.w(TAG, "Expected queued service but didn't find any");
- }
+ try {
+ servicesToBeDiscovered.removeFirst();
+ } catch (NoSuchElementException ex) {
+ Log.w(TAG, "Expected queued service but didn't find any");
}
leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(),
handleDiscoveredService + 1, discoveredService.endHandle + 1);
}
+ // Executes under "this" client mutex
private boolean executeMtuExchange()
{
if (Build.VERSION.SDK_INT >= 21) {
@@ -1076,7 +1139,7 @@ public class QtBluetoothLE {
} catch (Exception ex) {}
}
- Log.w(TAG, "Assuming default MTU value of 23 bytes");
+ Log.w(TAG, "Assuming default MTU value of 23 bytes");
mSupportedMtu = DEFAULT_MTU;
return true;
@@ -1091,9 +1154,7 @@ public class QtBluetoothLE {
newJob.jobType = IoJobType.Mtu;
newJob.entry = null;
- synchronized (readWriteQueue) {
- readWriteQueue.add(newJob);
- }
+ readWriteQueue.add(newJob);
performNextIO();
}
@@ -1104,7 +1165,6 @@ public class QtBluetoothLE {
Adds all Gatt entries for the given service to the readWriteQueue to be discovered.
This function only ever adds read requests to the queue.
- //TODO function not yet used
*/
private void scheduleServiceDetailDiscovery(int serviceHandle)
{
@@ -1117,47 +1177,42 @@ public class QtBluetoothLE {
return;
}
- synchronized (readWriteQueue) {
- // entire block inside mutex to ensure all service discovery jobs go in one after the other
- // ensures that serviceDiscovered() signal is sent when required
-
-
- // serviceHandle + 1 -> ignore service handle itself
- for (int i = serviceHandle + 1; i <= endHandle; i++) {
- GattEntry entry = entries.get(i);
-
- switch (entry.type) {
- case Characteristic:
- case Descriptor:
- // we schedule CharacteristicValue for initial discovery to simplify
- // detection of the end of service discovery process
- // performNextIO() ignores CharacteristicValue GATT entries
- case CharacteristicValue:
- break;
- case Service:
- // should not really happen unless endHandle is wrong
- Log.w(TAG, "scheduleServiceDetailDiscovery: wrong endHandle");
- return;
- }
+ // serviceHandle + 1 -> ignore service handle itself
+ for (int i = serviceHandle + 1; i <= endHandle; i++) {
+ GattEntry entry = entries.get(i);
+
+ switch (entry.type) {
+ case Characteristic:
+ case Descriptor:
+ // we schedule CharacteristicValue for initial discovery to simplify
+ // detection of the end of service discovery process
+ // performNextIO() ignores CharacteristicValue GATT entries
+ case CharacteristicValue:
+ break;
+ case Service:
+ // should not really happen unless endHandle is wrong
+ Log.w(TAG, "scheduleServiceDetailDiscovery: wrong endHandle");
+ return;
+ }
- // only descriptor and characteristic fall through to this point
- ReadWriteJob newJob = new ReadWriteJob();
- newJob.entry = entry;
- newJob.jobType = IoJobType.Read;
+ // only descriptor and characteristic fall through to this point
+ ReadWriteJob newJob = new ReadWriteJob();
+ newJob.entry = entry;
+ newJob.jobType = IoJobType.Read;
- final boolean result = readWriteQueue.add(newJob);
- if (!result)
- Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid()
- + " on item " + entry.type);
- }
+ final boolean result = readWriteQueue.add(newJob);
+ if (!result)
+ Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid()
+ + " on item " + entry.type);
}
}
/*************************************************************/
/* Write Characteristics */
+ /* This function is called from Qt thread */
/*************************************************************/
- public boolean writeCharacteristic(int charHandle, byte[] newValue,
+ public synchronized boolean writeCharacteristic(int charHandle, byte[] newValue,
int writeMode)
{
if (mBluetoothGatt == null)
@@ -1190,9 +1245,7 @@ public class QtBluetoothLE {
}
boolean result;
- synchronized (readWriteQueue) {
- result = readWriteQueue.add(newJob);
- }
+ result = readWriteQueue.add(newJob);
if (!result) {
Log.w(TAG, "Cannot add characteristic write request for " + charHandle + " to queue" );
@@ -1205,9 +1258,10 @@ public class QtBluetoothLE {
/*************************************************************/
/* Write Descriptors */
+ /* This function is called from Qt thread */
/*************************************************************/
- public boolean writeDescriptor(int descHandle, byte[] newValue)
+ public synchronized boolean writeDescriptor(int descHandle, byte[] newValue)
{
if (mBluetoothGatt == null)
return false;
@@ -1227,9 +1281,7 @@ public class QtBluetoothLE {
newJob.jobType = IoJobType.Write;
boolean result;
- synchronized (readWriteQueue) {
- result = readWriteQueue.add(newJob);
- }
+ result = readWriteQueue.add(newJob);
if (!result) {
Log.w(TAG, "Cannot add descriptor write request for " + descHandle + " to queue" );
@@ -1242,9 +1294,10 @@ public class QtBluetoothLE {
/*************************************************************/
/* Read Characteristics */
+ /* This function is called from Qt thread */
/*************************************************************/
- public boolean readCharacteristic(int charHandle)
+ public synchronized boolean readCharacteristic(int charHandle)
{
if (mBluetoothGatt == null)
return false;
@@ -1262,9 +1315,7 @@ public class QtBluetoothLE {
newJob.jobType = IoJobType.Read;
boolean result;
- synchronized (readWriteQueue) {
- result = readWriteQueue.add(newJob);
- }
+ result = readWriteQueue.add(newJob);
if (!result) {
Log.w(TAG, "Cannot add characteristic read request for " + charHandle + " to queue" );
@@ -1275,7 +1326,8 @@ public class QtBluetoothLE {
return true;
}
- public boolean readDescriptor(int descHandle)
+ // This function is called from Qt thread
+ public synchronized boolean readDescriptor(int descHandle)
{
if (mBluetoothGatt == null)
return false;
@@ -1293,9 +1345,7 @@ public class QtBluetoothLE {
newJob.jobType = IoJobType.Read;
boolean result;
- synchronized (readWriteQueue) {
- result = readWriteQueue.add(newJob);
- }
+ result = readWriteQueue.add(newJob);
if (!result) {
Log.w(TAG, "Cannot add descriptor read request for " + descHandle + " to queue" );
@@ -1309,12 +1359,10 @@ public class QtBluetoothLE {
// Called by TimeoutRunnable if the current I/O job timed out.
// By the time we reach this point the handleForTimeout counter has already been reset
// and the regular responses will be blocked off.
- private void interruptCurrentIO(int handle)
+ private synchronized void interruptCurrentIO(int handle)
{
//unlock the queue for next item
- synchronized (readWriteQueue) {
- pendingJob = null;
- }
+ pendingJob = null;
performNextIOThreaded();
@@ -1322,19 +1370,16 @@ public class QtBluetoothLE {
return;
try {
- synchronized (this) {
-
- GattEntry entry = entries.get(handle);
- if (entry == null)
- return;
- if (entry.valueKnown)
- return;
- entry.valueKnown = true;
+ GattEntry entry = entries.get(handle);
+ if (entry == null)
+ return;
+ if (entry.valueKnown)
+ return;
+ entry.valueKnown = true;
- GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
- if (serviceEntry != null && serviceEntry.endHandle == handle)
- finishCurrentServiceDiscovery(entry.associatedServiceHandle);
- }
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry != null && serviceEntry.endHandle == handle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
} catch (IndexOutOfBoundsException outOfBounds) {
Log.w(TAG, "interruptCurrentIO(): Unknown gatt entry, index: "
+ handle + " size: " + entries.size());
@@ -1364,7 +1409,7 @@ public class QtBluetoothLE {
cannot execute at the same time. The second write must happen after the
previous write has finished with on(Characteristic|Descriptor)Write().
*/
- private void performNextIO()
+ private synchronized void performNextIO()
{
if (mBluetoothGatt == null)
return;
@@ -1373,62 +1418,58 @@ public class QtBluetoothLE {
final ReadWriteJob nextJob;
int handle = HANDLE_FOR_RESET;
- synchronized (readWriteQueue) {
- if (readWriteQueue.isEmpty() || pendingJob != null)
- return;
-
- nextJob = readWriteQueue.remove();
- if (nextJob.jobType == IoJobType.Mtu) {
- handle = HANDLE_FOR_MTU_EXCHANGE; //mtu request is special case
- } else {
- switch (nextJob.entry.type) {
- case Characteristic:
- handle = handleForCharacteristic(nextJob.entry.characteristic);
- break;
- case Descriptor:
- handle = handleForDescriptor(nextJob.entry.descriptor);
- break;
- case CharacteristicValue:
- handle = nextJob.entry.endHandle;
- default:
- break;
- }
- }
+ if (readWriteQueue.isEmpty() || pendingJob != null)
+ return;
- // timeout handler and handleForTimeout atomic must be setup before
- // executing the request. Sometimes the callback is quicker than executing the
- // remainder of this function. Therefore enable the atomic early such that
- // callback handlers start hanging in the readWriteQueue sync block which
- // we are still occupying here.
- timeoutHandler.removeCallbacksAndMessages(null); // remove any timeout handlers
- handleForTimeout.set(modifiedReadWriteHandle(handle, nextJob.jobType));
-
- switch (nextJob.jobType) {
- case Read:
- skip = executeReadJob(nextJob);
+ nextJob = readWriteQueue.remove();
+ if (nextJob.jobType == IoJobType.Mtu) {
+ handle = HANDLE_FOR_MTU_EXCHANGE; //mtu request is special case
+ } else {
+ switch (nextJob.entry.type) {
+ case Characteristic:
+ handle = handleForCharacteristic(nextJob.entry.characteristic);
break;
- case Write:
- skip = executeWriteJob(nextJob);
+ case Descriptor:
+ handle = handleForDescriptor(nextJob.entry.descriptor);
break;
- case Mtu:
- skip = executeMtuExchange();
+ case CharacteristicValue:
+ handle = nextJob.entry.endHandle;
+ default:
break;
}
+ }
- if (skip) {
- handleForTimeout.set(HANDLE_FOR_RESET); // not a pending call -> release atomic
- } else {
- pendingJob = nextJob;
- timeoutHandler.postDelayed(new TimeoutRunnable(
- modifiedReadWriteHandle(handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
- }
+ // timeout handler and handleForTimeout atomic must be setup before
+ // executing the request. Sometimes the callback is quicker than executing the
+ // remainder of this function. Therefore enable the atomic early
+ timeoutHandler.removeCallbacksAndMessages(null); // remove any timeout handlers
+ handleForTimeout.set(modifiedReadWriteHandle(handle, nextJob.jobType));
- if (nextJob.jobType != IoJobType.Mtu) {
- Log.w(TAG, "Performing queued job, handle: " + handle + " " + nextJob.jobType + " (" +
- (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
- ") ValueKnown: " + nextJob.entry.valueKnown + " Skipping: " + skip +
- " " + nextJob.entry.type);
- }
+ switch (nextJob.jobType) {
+ case Read:
+ skip = executeReadJob(nextJob);
+ break;
+ case Write:
+ skip = executeWriteJob(nextJob);
+ break;
+ case Mtu:
+ skip = executeMtuExchange();
+ break;
+ }
+
+ if (skip) {
+ handleForTimeout.set(HANDLE_FOR_RESET); // not a pending call -> release atomic
+ } else {
+ pendingJob = nextJob;
+ timeoutHandler.postDelayed(new TimeoutRunnable(
+ modifiedReadWriteHandle(handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
+ }
+
+ if (nextJob.jobType != IoJobType.Mtu) {
+ Log.w(TAG, "Performing queued job, handle: " + handle + " " + nextJob.jobType + " (" +
+ (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
+ ") ValueKnown: " + nextJob.entry.valueKnown + " Skipping: " + skip +
+ " " + nextJob.entry.type);
}
GattEntry entry = nextJob.entry;
@@ -1477,15 +1518,13 @@ public class QtBluetoothLE {
}
// last entry of current discovery run?
- synchronized (this) {
- try {
- GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
- if (serviceEntry.endHandle == handle)
- finishCurrentServiceDiscovery(entry.associatedServiceHandle);
- } catch (IndexOutOfBoundsException outOfBounds) {
- Log.w(TAG, "performNextIO(): Unknown service for entry, index: "
- + entry.associatedServiceHandle + " size: " + entries.size());
- }
+ try {
+ GattEntry serviceEntry = entries.get(entry.associatedServiceHandle);
+ if (serviceEntry.endHandle == handle)
+ finishCurrentServiceDiscovery(entry.associatedServiceHandle);
+ } catch (IndexOutOfBoundsException outOfBounds) {
+ Log.w(TAG, "performNextIO(): Unknown service for entry, index: "
+ + entry.associatedServiceHandle + " size: " + entries.size());
}
} else {
int errorCode = 0;
@@ -1517,7 +1556,6 @@ public class QtBluetoothLE {
}
}
- // Runs inside the Mutex on readWriteQueue.
// Returns true if nextJob should be skipped.
private boolean executeWriteJob(ReadWriteJob nextJob)
{
@@ -1586,7 +1624,6 @@ public class QtBluetoothLE {
return false;
}
- // Runs inside the Mutex on readWriteQueue.
// Returns true if nextJob should be skipped.
private boolean executeReadJob(ReadWriteJob nextJob)
{
@@ -1664,8 +1701,8 @@ public class QtBluetoothLE {
return modifiedHandle;
}
- // Directly called from public Qt API
- public boolean requestConnectionUpdatePriority(double minimalInterval)
+ // This function is called from Qt thread
+ public synchronized boolean requestConnectionUpdatePriority(double minimalInterval)
{
if (mBluetoothGatt == null)
return false;
@@ -1706,4 +1743,3 @@ public class QtBluetoothLE {
public native void leCharacteristicChanged(long qtObject, int charHandle, byte[] newData);
public native void leServiceError(long qtObject, int attributeHandle, int errorCode);
}
-
diff --git a/src/bluetooth/bluez/hcimanager.cpp b/src/bluetooth/bluez/hcimanager.cpp
index e2635fae..a8b8e3b9 100644
--- a/src/bluetooth/bluez/hcimanager.cpp
+++ b/src/bluetooth/bluez/hcimanager.cpp
@@ -563,9 +563,11 @@ void HciManager::handleHciAclPacket(const quint8 *data, int size)
void HciManager::handleLeMetaEvent(const quint8 *data)
{
- // Spec v4.2, Vol 2, part E, 7.7.65ff
+ // Spec v5.3, Vol 4, part E, 7.7.65.*
switch (*data) {
- case 0x1: {
+ case 0x1: // HCI_LE_Connection_Complete
+ case 0xA: // HCI_LE_Enhanced_Connection_Complete
+ {
const quint16 handle = bt_get_le16(data + 2);
emit connectionComplete(handle);
break;
diff --git a/src/bluetooth/osx/osxbtconnectionmonitor.mm b/src/bluetooth/osx/osxbtconnectionmonitor.mm
index f41bbed5..159bceb5 100644
--- a/src/bluetooth/osx/osxbtconnectionmonitor.mm
+++ b/src/bluetooth/osx/osxbtconnectionmonitor.mm
@@ -96,6 +96,15 @@ using namespace QT_NAMESPACE;
if (!device)
return;
+ if (!monitor) {
+ // Rather surprising: monitor == nullptr means we stopped monitoring.
+ // So apparently this thingie is still alive and keeps receiving
+ // notifications.
+ qCWarning(QT_BT_OSX,
+ "Connection notification received in a monitor that was cancelled");
+ return;
+ }
+
QT_BT_MAC_AUTORELEASEPOOL;
// All Obj-C objects are autoreleased.
diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm
index 2fd0d2db..4757331c 100644
--- a/src/bluetooth/osx/osxbtdeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm
@@ -112,11 +112,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
m_active = true;
[m_inquiry clearFoundDevices];
+
+ qCDebug(QT_BT_OSX) << "Starting device inquiry with"
+ << IOBlueoothInquiryLengthS << "second timeout limit.";
const IOReturn result = [m_inquiry start];
if (result != kIOReturnSuccess) {
// QtBluetooth will probably convert an error into UnknownError,
// losing the actual information.
- qCWarning(QT_BT_OSX) << "failed with IOKit error code:" << result;
+ qCWarning(QT_BT_OSX) << "device inquiry start failed with IOKit error code:" << result;
m_active = false;
} else {
// Docs say it's 10 s. by default, we set it to 15 s. (see -initWithDelegate:),
@@ -124,11 +127,14 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
watchDog.reset(new QTimer);
watchDog->connect(watchDog.get(), &QTimer::timeout, watchDog.get(), [self]{
qCWarning(QT_BT_OSX, "Manually interrupting IOBluetoothDeviceInquiry");
+ qCDebug(QT_BT_OSX) << "Found devices:" << [m_inquiry foundDevices];
[self stop];
});
watchDog->setSingleShot(true);
- watchDog->setInterval(IOBlueoothInquiryLengthS * 2 * 1000); // Let's make it twice as long.
+ // Let's use 17 s. so that IOBluetooth, if it respects 'inquiryLength'
+ // has a chance to stop before we do:
+ watchDog->setInterval((IOBlueoothInquiryLengthS + 2) * 1000);
watchDog->start();
}
@@ -148,6 +154,8 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender
error:(IOReturn)error aborted:(BOOL)aborted
{
+ qCDebug(QT_BT_OSX) << "deviceInquiryComplete, error:" << error
+ << "user-stopped:" << aborted;
if (!m_active)
return;
@@ -158,11 +166,11 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
if (error != kIOReturnSuccess && !aborted) {
// QtBluetooth has not too many error codes, 'UnknownError' is not really
- // useful, report the actual error code here:
+ // useful, log the actual error code here:
qCWarning(QT_BT_OSX) << "IOKit error code: " << error;
- m_delegate->error(error);
// Let watchDog to stop it, calling -stop at timeout, otherwise,
- // it looks like inquiry continues and keeps reporting new devices found.
+ // it looks like inquiry continues even after this error and
+ // keeps reporting new devices found.
} else {
// Either a normal completion or from a timer slot.
watchDog.reset();
@@ -174,6 +182,7 @@ const uint8_t IOBlueoothInquiryLengthS = 15;
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry *)sender
device:(IOBluetoothDevice *)device
{
+ qCDebug(QT_BT_OSX) << "deviceInquiryDeviceFound:" << [device nameOrAddress];
if (sender != m_inquiry) // Can never happen in the current version.
return;
diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm
index a2b02b1a..e60b9787 100644
--- a/src/bluetooth/osx/osxbtsdpinquiry.mm
+++ b/src/bluetooth/osx/osxbtsdpinquiry.mm
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
@@ -43,8 +43,12 @@
#include "osxbtutility_p.h"
#include "btdelegates_p.h"
+#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/qvariant.h>
#include <QtCore/qstring.h>
+#include <QtCore/qtimer.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
@@ -52,6 +56,8 @@ namespace OSXBluetooth {
namespace {
+const int basebandConnectTimeoutMS = 20000;
+
QBluetoothUuid sdp_element_to_uuid(IOBluetoothSDPDataElement *element)
{
QT_BT_MAC_AUTORELEASEPOOL;
@@ -78,11 +84,20 @@ QVector<QBluetoothUuid> extract_service_class_ID_list(IOBluetoothSDPServiceRecor
QT_BT_MAC_AUTORELEASEPOOL;
IOBluetoothSDPDataElement *const idList = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList];
- if (!idList || [idList getTypeDescriptor] != kBluetoothSDPDataElementTypeDataElementSequence)
- return {};
QVector<QBluetoothUuid> uuids;
- NSArray *const arr = [idList getArrayValue];
+ if (!idList)
+ return uuids;
+
+ NSArray *arr = nil;
+ if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeDataElementSequence)
+ arr = [idList getArrayValue];
+ else if ([idList getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID)
+ arr = @[idList];
+
+ if (!arr)
+ return uuids;
+
for (IOBluetoothSDPDataElement *dataElement in arr) {
const auto qtUuid = sdp_element_to_uuid(dataElement);
if (!qtUuid.isNull())
@@ -200,7 +215,7 @@ QVector<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device)
return uuids;
}
-}
+} // namespace OSXBluetooth
QT_END_NAMESPACE
@@ -213,6 +228,9 @@ using namespace OSXBluetooth;
QT_PREPEND_NAMESPACE(DarwinBluetooth::SDPInquiryDelegate) *delegate;
IOBluetoothDevice *device;
bool isActive;
+
+ // Needed to workaround a broken SDP on Monterey:
+ std::unique_ptr<QTimer> connectionWatchdog;
}
- (id)initWithDelegate:(DarwinBluetooth::SDPInquiryDelegate *)aDelegate
@@ -242,17 +260,41 @@ using namespace OSXBluetooth;
return [self performSDPQueryWithDevice:address filters:emptyFilter];
}
+- (void)interruptSDPQuery
+{
+ // To be only executed on timer.
+ Q_ASSERT(connectionWatchdog.get());
+ // If device was reset, so the timer should be, we can never be here then.
+ Q_ASSERT(device);
+
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ qCDebug(QT_BT_OSX) << "couldn't connect to device" << [device nameOrAddress]
+ << ", ending SDP inquiry.";
+
+ // Stop the watchdog and close the connection as otherwise there could be
+ // later "connectionComplete" callbacks
+ connectionWatchdog->stop();
+ [device closeConnection];
+
+ delegate->SDPInquiryError(device, kIOReturnTimeout);
+ [device release];
+ device = nil;
+ isActive = false;
+}
+
- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address
filters:(const QList<QBluetoothUuid> &)qtFilters
{
Q_ASSERT_X(!isActive, Q_FUNC_INFO, "SDP query in progress");
Q_ASSERT_X(!address.isNull(), Q_FUNC_INFO, "invalid target device address");
+ qCDebug(QT_BT_OSX) << "Starting and SDP inquiry for address:" << address;
QT_BT_MAC_AUTORELEASEPOOL;
// We first try to allocate "filters":
ObjCScopedPointer<NSMutableArray> array;
- if (qtFilters.size()) {
+ if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSBigSur
+ && qtFilters.size()) { // See the comment about filters on Monterey below.
array.reset([[NSMutableArray alloc] init]);
if (!array) {
qCCritical(QT_BT_OSX) << "failed to allocate an uuid filter";
@@ -281,7 +323,65 @@ using namespace OSXBluetooth;
ObjCScopedPointer<IOBluetoothDevice> oldDevice(device);
device = newDevice.data();
+ qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress] << "connected:"
+ << bool([device isConnected]) << "paired:" << bool([device isPaired]);
IOReturn result = kIOReturnSuccess;
+
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) {
+ // SDP query on Monterey does not follow its own documented/expected behavior:
+ // - a simple performSDPQuery was previously ensuring baseband connection
+ // to be opened, now it does not do so, instead logs a warning and returns
+ // immediately.
+ // - a version with UUID filters simply does nothing except it immediately
+ // returns kIOReturnSuccess.
+
+ // If the device was not yet connected, connect it first
+ if (![device isConnected]) {
+ qCDebug(QT_BT_OSX) << "Device" << [device nameOrAddress]
+ << "is not connected, connecting it first";
+ result = [device openConnection:self];
+ // The connection may succeed immediately. But if it didn't, start a connection timer
+ // which has two guardian roles:
+ // 1. Guard against connect attempt taking too long time
+ // 2. Sometimes on Monterey the callback indicating "connection completion" is
+ // not received even though the connection has in fact succeeded
+ if (![device isConnected]) {
+ qCDebug(QT_BT_OSX) << "Starting connection monitor for device"
+ << [device nameOrAddress] << "with timeout limit of"
+ << basebandConnectTimeoutMS/1000 << "seconds.";
+ connectionWatchdog.reset(new QTimer);
+ connectionWatchdog->setSingleShot(false);
+ QObject::connect(connectionWatchdog.get(), &QTimer::timeout,
+ connectionWatchdog.get(),
+ [self] () {
+ qCDebug(QT_BT_OSX) << "Connection monitor timeout for device:"
+ << [device nameOrAddress]
+ << ", connected:" << bool([device isConnected]);
+ // Device can sometimes get properly connected without IOBluetooth
+ // calling the connectionComplete callback, so we check the status here
+ if ([device isConnected])
+ [self connectionComplete:device status:kIOReturnSuccess];
+ else
+ [self interruptSDPQuery];
+ });
+ connectionWatchdog->start(basebandConnectTimeoutMS);
+ }
+ }
+
+ if ([device isConnected])
+ result = [device performSDPQuery:self];
+
+ if (result != kIOReturnSuccess) {
+ qCCritical(QT_BT_OSX, "failed to start an SDP query");
+ device = oldDevice.take();
+ } else {
+ newDevice.take();
+ isActive = true;
+ }
+
+ return result;
+ } // Monterey's code path.
+
if (qtFilters.size())
result = [device performSDPQuery:self uuids:array];
else
@@ -291,25 +391,53 @@ using namespace OSXBluetooth;
qCCritical(QT_BT_OSX) << "failed to start an SDP query";
device = oldDevice.take();
} else {
- isActive = true;
newDevice.take();
+ isActive = true;
}
return result;
}
-- (void)stopSDPQuery
+- (void)connectionComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
{
- // There is no API to stop it,
- // but there is a 'stop' member-function in Qt and
- // after it's called sdpQueryComplete must be somehow ignored.
+ qCDebug(QT_BT_OSX) << "connectionComplete for device" << [aDevice nameOrAddress]
+ << "with status:" << status;
+ if (aDevice != device) {
+ // Connection was previously cancelled, probably, due to the timeout.
+ return;
+ }
+
+ // The connectionComplete may be invoked by either the IOBluetooth callback or our
+ // connection watchdog. In either case stop the watchdog if it exists
+ if (connectionWatchdog)
+ connectionWatchdog->stop();
+ if (status == kIOReturnSuccess)
+ status = [aDevice performSDPQuery:self];
+
+ if (status != kIOReturnSuccess) {
+ isActive = false;
+ qCWarning(QT_BT_OSX, "failed to open connection or start an SDP query");
+ Q_ASSERT_X(delegate, Q_FUNC_INFO, "invalid delegate (null)");
+ delegate->SDPInquiryError(aDevice, status);
+ }
+}
+
+- (void)stopSDPQuery
+{
+ // There is no API to stop it SDP on device, but there is a 'stop'
+ // member-function in Qt and after it's called sdpQueryComplete
+ // must be somehow ignored (device != aDevice in a callback).
[device release];
device = nil;
+ isActive = false;
+ connectionWatchdog.reset();
}
- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status
{
+ qCDebug(QT_BT_OSX) << "sdpQueryComplete for device:" << [aDevice nameOrAddress]
+ << "with status:" << status;
// Can happen - there is no legal way to cancel an SDP query,
// after the 'reset' device can never be
// the same as the cancelled one.
@@ -320,6 +448,15 @@ using namespace OSXBluetooth;
isActive = false;
+ // If we used the manual connection establishment, close the
+ // connection here. Otherwise the IOBluetooth may call stray
+ // connectionComplete or sdpQueryCompletes
+ if (connectionWatchdog) {
+ qCDebug(QT_BT_OSX) << "Closing the connection established for SDP inquiry.";
+ connectionWatchdog.reset();
+ [device closeConnection];
+ }
+
if (status != kIOReturnSuccess)
delegate->SDPInquiryError(aDevice, status);
else
@@ -327,3 +464,4 @@ using namespace OSXBluetooth;
}
@end
+
diff --git a/src/bluetooth/osx/osxbtservicerecord.mm b/src/bluetooth/osx/osxbtservicerecord.mm
index 23d55d13..2d868f63 100644
--- a/src/bluetooth/osx/osxbtservicerecord.mm
+++ b/src/bluetooth/osx/osxbtservicerecord.mm
@@ -146,7 +146,7 @@ void add_attribute(const QVariant &var, AttributeId key, Dictionary dict)
return;
const Number num(variant_to_nsnumber<ValueType>(var));
- [dict setObject:num forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:num forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
template<>
@@ -160,7 +160,7 @@ void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dic
const QString string(var.value<QString>());
if (string.length()) {
if (NSString *const nsString = string.toNSString())
- [dict setObject:nsString forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:nsString forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
}
@@ -173,7 +173,7 @@ void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Diction
return;
SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
- [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%d", int(key)]];
+ [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%x", int(key)]];
}
template<>
@@ -207,6 +207,25 @@ void add_attribute(const QVariant &var, NSMutableArray *list)
}
template<>
+void add_attribute<unsigned short>(const QVariant &var, NSMutableArray *list)
+{
+ Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
+
+ if (!var.canConvert<unsigned short>())
+ return;
+
+ const Number num(variant_to_nsnumber<unsigned short>(var));
+
+ NSDictionary* dict = @{
+ @"DataElementType" : [NSNumber numberWithInt:1],
+ @"DataElementSize" : [NSNumber numberWithInt:2],
+ @"DataElementValue" : num
+ };
+
+ [list addObject: dict];
+}
+
+template<>
void add_attribute<QString>(const QVariant &var, NSMutableArray *list)
{
Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");
@@ -273,7 +292,7 @@ void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict)
[rfcommList addObject:rfcommDict];
[descriptorList addObject:rfcommList];
- [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}
@@ -299,7 +318,7 @@ void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict)
[l2capList addObject:l2capDict];
[descriptorList addObject:l2capList];
- [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d",
+ [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}
@@ -310,10 +329,10 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
if (var.canConvert<Sequence>())
return false;
- if (var.canConvert<QString>()) {
+ if (var.type() == QVariant::String) {
//ServiceName, ServiceDescription, ServiceProvider.
add_attribute<QString>(var, list);
- } else if (var.canConvert<QBluetoothUuid>()) {
+ } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
add_attribute<QBluetoothUuid>(var, list);
} else {
// Here we need 'key' to understand the type.
@@ -325,6 +344,9 @@ bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
case QSInfo::ServiceInfoTimeToLive:
add_attribute<unsigned>(var, list);
break;
+ case QSInfo::BluetoothProfileDescriptorList:
+ add_attribute<unsigned short>(var, list);
+ break;
case QSInfo::ServiceAvailability:
add_attribute<unsigned char>(var, list);
break;
@@ -348,10 +370,10 @@ bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Di
if (var.canConvert<Sequence>())
return false;
- if (var.canConvert<QString>()) {
+ if (var.type() == QVariant::String) {
//ServiceName, ServiceDescription, ServiceProvider.
add_attribute<QString>(var, key, dict);
- } else if (var.canConvert<QBluetoothUuid>()) {
+ } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict);
} else {
// We can have different integer types actually, so I have to check
@@ -385,14 +407,15 @@ bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray
if (var.isNull() || !var.canConvert<Sequence>())
return false;
+ NSMutableArray *const nested = [NSMutableArray array];
+ [list addObject:nested];
+
const Sequence sequence(var.value<Sequence>());
for (const QVariant &var : sequence) {
if (var.canConvert<Sequence>()) {
- NSMutableArray *const nested = [NSMutableArray array];
add_sequence_attribute(var, key, nested);
- [list addObject:nested];
} else {
- add_attribute(var, key, list);
+ add_attribute(var, key, nested);
}
}
@@ -415,8 +438,7 @@ bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeI
if (!add_sequence_attribute(element, key, list))
add_attribute(element, key, list);
}
- [dict setObject:list forKey:[NSString stringWithFormat:@"%d", int(key)]];
-
+ [dict setObject:list forKey:[NSString stringWithFormat:@"%x", int(key)]];
return true;
}
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
index 66834827..82a2ebbc 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
@@ -157,6 +157,8 @@ private:
#endif
HRESULT onBluetoothLEAdvertisementReceived(IBluetoothLEAdvertisementReceivedEventArgs *args);
+ void decrementPairedDevicesAndCheckFinished();
+
public slots:
void finishDiscovery();
@@ -492,10 +494,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync
// on Windows 10 FromIdAsync might ask for device permission. We cannot wait here but have to handle that asynchronously
HRESULT hr = m_deviceStatics->FromIdAsync(deviceId, &deviceFromIdOperation);
if (FAILED(hr)) {
- --m_pendingPairedDevices;
- if (!m_pendingPairedDevices
- && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod))
- finishDiscovery();
+ decrementPairedDevicesAndCheckFinished();
qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id";
return S_OK;
}
@@ -506,15 +505,13 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync
if (thisPointer) {
if (status == Completed)
thisPointer->onPairedClassicBluetoothDeviceFoundAsync(op, status);
- --thisPointer->m_pendingPairedDevices;
+ else
+ thisPointer->decrementPairedDevicesAndCheckFinished();
}
return S_OK;
}).Get());
if (FAILED(hr)) {
- --m_pendingPairedDevices;
- if (!m_pendingPairedDevices
- && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod))
- finishDiscovery();
+ decrementPairedDevicesAndCheckFinished();
qCWarning(QT_BT_WINRT) << "Could not register device found callback";
return S_OK;
}
@@ -522,7 +519,7 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync
});
if (FAILED(hr)) {
emit errorOccured(QBluetoothDeviceDiscoveryAgent::UnknownError);
- --m_pendingPairedDevices;
+ decrementPairedDevicesAndCheckFinished();
qCWarning(QT_BT_WINRT) << "Could not obtain bluetooth device from id";
return;
}
@@ -531,6 +528,10 @@ void QWinRTBluetoothDeviceDiscoveryWorker::classicBluetoothInfoFromDeviceIdAsync
// "deviceFound" will be emitted at the end of the deviceFromIdOperation callback
void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTRING deviceId)
{
+ // Note: in this method we do not need to call
+ // decrementPairedDevicesAndCheckFinished() because we *do* run LE
+ // scanning, so the condition in the check will always be false.
+ // It's enough to just decrement m_pendingPairedDevices.
HRESULT hr;
hr = QEventDispatcherWinRT::runOnXamlThread([deviceId, this]() {
ComPtr<IAsyncOperation<BluetoothLEDevice *>> deviceFromIdOperation;
@@ -548,7 +549,8 @@ void QWinRTBluetoothDeviceDiscoveryWorker::leBluetoothInfoFromDeviceIdAsync(HSTR
if (thisPointer) {
if (status == Completed)
thisPointer->onPairedBluetoothLEDeviceFoundAsync(op, status);
- --thisPointer->m_pendingPairedDevices;
+ else
+ --thisPointer->m_pendingPairedDevices;
}
return S_OK;
}).Get());
@@ -687,6 +689,14 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedClassicBluetoothDeviceFoun
return S_OK;
}
+void QWinRTBluetoothDeviceDiscoveryWorker::decrementPairedDevicesAndCheckFinished()
+{
+ if ((--m_pendingPairedDevices == 0)
+ && !(requestedModes & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod)) {
+ finishDiscovery();
+ }
+}
+
HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onPairedBluetoothLEDeviceFoundAsync(IAsyncOperation<BluetoothLEDevice *> *op, AsyncStatus status)
{
--m_pendingPairedDevices;
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
index 3ab0d580..96da5ddc 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
@@ -274,9 +274,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
Q_Q(QBluetoothServiceDiscoveryAgent);
QTimer::singleShot(4000, q, [this]() {
this->_q_fetchUuidsTimeout();
- });
+ }); // will also call _q_seriveDiscoveryFinished()
+ } else {
+ _q_serviceDiscoveryFinished();
}
- _q_serviceDiscoveryFinished();
return;
}
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
index 6a93143b..4a18cfc7 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
@@ -320,7 +320,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceD
for (const QBluetoothUuid &id : serviceClassUuids) {
if (id.minimumSize() == 16) {
serviceInfo.setServiceUuid(id);
- serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
+ if (serviceInfo.serviceName().isEmpty()) {
+ serviceInfo.setServiceName(
+ QBluetoothServiceDiscoveryAgent::tr("Custom Service"));
+ }
QBluetoothServiceInfo::Sequence modSeq =
serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds).value<QBluetoothServiceInfo::Sequence>();
modSeq.removeOne(QVariant::fromValue(id));
@@ -334,8 +337,10 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceD
qCDebug(QT_BT_BLUEZ) << "Discovered services" << discoveredDevices.at(0).address().toString()
<< serviceInfo.serviceName() << serviceInfo.serviceUuid()
<< ">>>" << serviceInfo.serviceClassUuids();
-
- emit q->serviceDiscovered(serviceInfo);
+ // Use queued connection to allow us finish the service looping; the application
+ // might call stop() when it has detected the service-of-interest.
+ QMetaObject::invokeMethod(q, "serviceDiscovered", Qt::QueuedConnection,
+ Q_ARG(QBluetoothServiceInfo, serviceInfo));
}
}
}
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
index d8decae1..96034ca7 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm
@@ -47,6 +47,7 @@
#include "osx/osxbluetooth_p.h"
#include "osx/uistrings_p.h"
+#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qstring.h>
@@ -148,15 +149,37 @@ void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(void *generic)
QT_BT_MAC_AUTORELEASEPOOL;
NSArray *const records = device.services;
+ qCDebug(QT_BT_OSX) << "SDP finished for device" << [device nameOrAddress]
+ << ", services found:" << [records count];
for (IOBluetoothSDPServiceRecord *record in records) {
QBluetoothServiceInfo serviceInfo;
Q_ASSERT_X(discoveredDevices.size() >= 1, Q_FUNC_INFO, "invalid number of devices");
+ qCDebug(QT_BT_OSX) << "Processing service" << [record getServiceName];
serviceInfo.setDevice(discoveredDevices.at(0));
OSXBluetooth::extract_service_record(record, serviceInfo);
- if (!serviceInfo.isValid())
+ if (!serviceInfo.isValid()) {
+ qCDebug(QT_BT_OSX) << "Discarding invalid service";
continue;
+ }
+
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur
+ && uuidFilter.size()) {
+ const auto &serviceId = serviceInfo.serviceUuid();
+ bool match = !serviceId.isNull() && uuidFilter.contains(serviceId);
+ if (!match) {
+ const auto &classUuids = serviceInfo.serviceClassUuids();
+ for (const auto &uuid : classUuids) {
+ if (uuidFilter.contains(uuid)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ continue;
+ }
+ }
if (!isDuplicatedService(serviceInfo)) {
discoveredServices.append(serviceInfo);
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
index 41410b70..0527cdde 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
@@ -239,10 +239,10 @@ private:
private slots:
void processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info);
void onScanFinished(quint64 deviceAddress);
- void onScanCanceled();
void onError();
private:
+ void releaseWorker();
QPointer<QWinRTBluetoothServiceDiscoveryWorker> worker;
#endif
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp
index f1476758..e8d64efc 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp
@@ -82,6 +82,13 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
#define TYPE_STRING 37
#define TYPE_SEQUENCE 53
+// Helper to reverse given uchar array
+static void reverseArray(uchar data[], size_t length)
+{
+ for (size_t i = length; i > length/2; i--)
+ std::swap(data[length - i], data[i - 1]);
+}
+
class QWinRTBluetoothServiceDiscoveryWorker : public QObject
{
Q_OBJECT
@@ -94,7 +101,6 @@ public:
Q_SIGNALS:
void serviceFound(quint64 deviceAddress, const QBluetoothServiceInfo &info);
void scanFinished(quint64 deviceAddress);
- void scanCanceled();
void errorOccured();
private:
@@ -309,6 +315,8 @@ void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 a
GUID value;
hr = dataReader->ReadGuid(&value);
Q_ASSERT_SUCCEEDED(hr);
+ // The latter 8 bytes are in reverse order
+ reverseArray(value.Data4, sizeof(value.Data4)/sizeof(value.Data4[0]));
const QBluetoothUuid uuid(value);
info.setAttribute(key, uuid);
qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UUID" << hex << uuid;
@@ -422,7 +430,8 @@ QBluetoothServiceInfo::Sequence QWinRTBluetoothServiceDiscoveryWorker::readSeque
GUID b;
hr = dataReader->ReadGuid(&b);
Q_ASSERT_SUCCEEDED(hr);
-
+ // The latter 8 bytes are in reverse order
+ reverseArray(b.Data4, sizeof(b.Data4)/sizeof(b.Data4[0]));
const QBluetoothUuid uuid(b);
result.append(QVariant::fromValue(uuid));
remainingLength -= sizeof(GUID);
@@ -494,7 +503,7 @@ QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
{
- stop();
+ releaseWorker();
}
void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
@@ -508,8 +517,6 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr
this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService, Qt::QueuedConnection);
connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished, Qt::QueuedConnection);
- connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled,
- this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled, Qt::QueuedConnection);
connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
this, &QBluetoothServiceDiscoveryAgentPrivate::onError, Qt::QueuedConnection);
worker->start();
@@ -517,20 +524,9 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr
void QBluetoothServiceDiscoveryAgentPrivate::stop()
{
- if (!worker)
- return;
-
- disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
- this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService);
- disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
- this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished);
- disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled,
- this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled);
- disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
- this, &QBluetoothServiceDiscoveryAgentPrivate::onError);
- // mWorker will delete itself as soon as it is done with its discovery
- worker = nullptr;
- setDiscoveryState(Inactive);
+ releaseWorker();
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ emit q->canceled();
}
void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info)
@@ -579,26 +575,12 @@ void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceA
void QBluetoothServiceDiscoveryAgentPrivate::onScanFinished(quint64 deviceAddress)
{
- Q_Q(QBluetoothServiceDiscoveryAgent);
- bool deviceFound;
- for (const QBluetoothDeviceInfo &deviceInfo : qAsConst(discoveredDevices)) {
- if (deviceInfo.address().toUInt64() == deviceAddress) {
- deviceFound = true;
- discoveredDevices.removeOne(deviceInfo);
- if (discoveredDevices.isEmpty())
- setDiscoveryState(Inactive);
- break;
- }
- }
- Q_ASSERT(deviceFound);
- stop();
- emit q->finished();
-}
-
-void QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled()
-{
- Q_Q(QBluetoothServiceDiscoveryAgent);
- emit q->canceled();
+ // The scan for a device's services has finished. Disconnect the
+ // worker and call the baseclass function which starts the scan for
+ // the next device if there are any unscanned devices left (or finishes
+ // the scan if none left)
+ releaseWorker();
+ _q_serviceDiscoveryFinished();
}
void QBluetoothServiceDiscoveryAgentPrivate::onError()
@@ -610,6 +592,21 @@ void QBluetoothServiceDiscoveryAgentPrivate::onError()
emit q->error(error);
}
+void QBluetoothServiceDiscoveryAgentPrivate::releaseWorker()
+{
+ if (!worker)
+ return;
+
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService);
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished);
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onError);
+ // mWorker will delete itself as soon as it is done with its discovery
+ worker = nullptr;
+}
+
QT_END_NAMESPACE
#include <qbluetoothservicediscoveryagent_winrt.moc>
diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp
index e4d85447..60a64375 100644
--- a/src/bluetooth/qbluetoothsocket.cpp
+++ b/src/bluetooth/qbluetoothsocket.cpp
@@ -95,6 +95,11 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT)
On iOS, this class cannot be used because the platform does not expose
an API which may permit access to QBluetoothSocket related features.
+
+ \note On macOS Monterey (12) the socket data flow is paused when a
+ modal dialogue is executing, or an event tracking mode is entered (for
+ example by long-pressing a Window close button). This may change in the
+ future releases of macOS.
*/
/*!
@@ -593,7 +598,7 @@ void QBluetoothSocket::setSocketState(QBluetoothSocket::SocketState state)
bool QBluetoothSocket::canReadLine() const
{
Q_D(const QBluetoothSocketBase);
- return d->canReadLine();
+ return d->canReadLine() || QIODevice::canReadLine();
}
/*!
diff --git a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp
index 084aa958..d6aa17a7 100644
--- a/src/bluetooth/qbluetoothsocket_bluezdbus.cpp
+++ b/src/bluetooth/qbluetoothsocket_bluezdbus.cpp
@@ -284,6 +284,10 @@ void QBluetoothSocketPrivateBluezDBus::connectToService(
return;
}
+ if (service.socketProtocol() != QBluetoothServiceInfo::Protocol::UnknownProtocol)
+ socketType = service.socketProtocol();
+ qCDebug(QT_BT_BLUEZ) << "Socket protocol used:" << socketType;
+
connectToService(service.device().address(), targetService, openMode);
}
diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp
index d562f6f7..e7a42f01 100644
--- a/src/bluetooth/qbluetoothsocket_winrt.cpp
+++ b/src/bluetooth/qbluetoothsocket_winrt.cpp
@@ -50,6 +50,7 @@
#include <QtBluetooth/qbluetoothdeviceinfo.h>
#include <QtBluetooth/qbluetoothserviceinfo.h>
#include <QtCore/qloggingcategory.h>
+#include <QtCore/QPointer>
#include <robuffer.h>
#include <windows.devices.bluetooth.h>
@@ -141,20 +142,6 @@ public:
void close()
{
m_shuttingDown = true;
- if (Q_UNLIKELY(m_initialReadOp)) {
- onReadyRead(m_initialReadOp.Get(), Canceled);
- ComPtr<IAsyncInfo> info;
- HRESULT hr = m_initialReadOp.As(&info);
- Q_ASSERT_SUCCEEDED(hr);
- if (info) {
- hr = info->Cancel();
- Q_ASSERT_SUCCEEDED(hr);
- hr = info->Close();
- Q_ASSERT_SUCCEEDED(hr);
- }
- m_initialReadOp.Reset();
- }
-
if (m_readOp) {
onReadyRead(m_readOp.Get(), Canceled);
ComPtr<IAsyncInfo> info;
@@ -195,9 +182,16 @@ public:
ComPtr<IInputStream> stream;
hr = m_socket->get_InputStream(&stream);
Q_ASSERT_SUCCEEDED(hr);
- hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_initialReadOp.GetAddressOf());
+ hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_readOp.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
- hr = m_initialReadOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get());
+ QPointer<SocketWorker> thisPtr(this);
+ hr = m_readOp->put_Completed(
+ Callback<SocketReadCompletedHandler>([thisPtr](IAsyncBufferOperation *asyncInfo,
+ AsyncStatus status) {
+ if (thisPtr)
+ return thisPtr->onReadyRead(asyncInfo, status);
+ return S_OK;
+ }).Get());
Q_ASSERT_SUCCEEDED(hr);
return S_OK;
});
@@ -209,13 +203,10 @@ public:
if (m_shuttingDown)
return S_OK;
- if (asyncInfo == m_initialReadOp.Get()) {
- m_initialReadOp.Reset();
- } else if (asyncInfo == m_readOp.Get()) {
+ if (asyncInfo == m_readOp.Get())
m_readOp.Reset();
- } else {
+ else
Q_ASSERT(false);
- }
// A read in UnconnectedState will close the socket and return -1 and thus tell the caller,
// that the connection was closed. The socket cannot be closed here, as the subsequent read
@@ -301,7 +292,14 @@ public:
emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
return S_OK;
}
- hr = m_readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get());
+ QPointer<SocketWorker> thisPtr(this);
+ hr = m_readOp->put_Completed(
+ Callback<SocketReadCompletedHandler>([thisPtr](IAsyncBufferOperation *asyncInfo,
+ AsyncStatus status) {
+ if (thisPtr)
+ return thisPtr->onReadyRead(asyncInfo, status);
+ return S_OK;
+ }).Get());
if (FAILED(hr)) {
qErrnoWarning(hr, "onReadyRead(): Failed to set socket read callback.");
emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
@@ -323,7 +321,6 @@ private:
// Protects pendingData/pendingDatagrams which are accessed from native callbacks
QMutex m_mutex;
- ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_initialReadOp;
ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_readOp;
};
diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp
index 42f4380e..6fd0ed2a 100644
--- a/src/bluetooth/qlowenergycontroller_winrt_new.cpp
+++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp
@@ -404,6 +404,8 @@ public:
~QWinRTLowEnergyConnectionHandler()
{
qCDebug(QT_BT_WINRT) << __FUNCTION__;
+ mDevice.Reset();
+ mGattSession.Reset();
// To close the COM library gracefully, each successful call to
// CoInitialize, including those that return S_FALSE, must be balanced
// by a corresponding call to CoUninitialize.
@@ -436,7 +438,7 @@ private:
void QWinRTLowEnergyConnectionHandler::connectToDevice()
{
qCDebug(QT_BT_WINRT) << __FUNCTION__;
- mInitialized = CoInitialize(NULL);
+ mInitialized = CoInitializeEx(NULL, COINIT_MULTITHREADED);
qCDebug(QT_BT_WINRT) << qt_error_string(mInitialized);
auto earlyExit = [this]() { return mAbortConnection; };
@@ -676,10 +678,23 @@ void QWinRTLowEnergyConnectionHandler::emitConnectedAndQuitThread()
QThread::currentThread()->quit();
}
+static void registerServiceHandlerMetaTypes()
+{
+ static bool registered = false;
+ if (!registered) {
+ qRegisterMetaType<QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>>(
+ "QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>");
+ qRegisterMetaType<QVector<QBluetoothUuid>>("QVector<QBluetoothUuid>");
+ qRegisterMetaType<QLowEnergyHandle>("QLowEnergyHandle");
+ registered = true;
+ }
+}
+
QLowEnergyControllerPrivateWinRTNew::QLowEnergyControllerPrivateWinRTNew()
: QLowEnergyControllerPrivate()
{
registerQLowEnergyControllerMetaType();
+ registerServiceHandlerMetaTypes();
connect(this, &QLowEnergyControllerPrivateWinRTNew::characteristicChanged,
this, &QLowEnergyControllerPrivateWinRTNew::handleCharacteristicChanged,
Qt::QueuedConnection);
diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h
index 226af145..1816a23f 100644
--- a/src/bluetooth/qlowenergyserviceprivate_p.h
+++ b/src/bluetooth/qlowenergyserviceprivate_p.h
@@ -146,5 +146,6 @@ typedef QHash<QLowEnergyHandle, QLowEnergyServicePrivate::DescData> DescriptorDa
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QSharedPointer<QLowEnergyServicePrivate>)
+Q_DECLARE_METATYPE(QLowEnergyServicePrivate::CharData)
#endif // QLOWENERGYSERVICEPRIVATE_P_H
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
index 900bb5e9..e221ba74 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
+++ b/tests/auto/qbluetoothdevicediscoveryagent/qbluetoothdevicediscoveryagent.pro
@@ -2,5 +2,7 @@ SOURCES += tst_qbluetoothdevicediscoveryagent.cpp
TARGET=tst_qbluetoothdevicediscoveryagent
CONFIG += testcase
+macos: QMAKE_INFO_PLIST = ../shared/Info.macos.plist
+
QT = core concurrent bluetooth-private testlib
osx:QT += widgets
diff --git a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
index cbce5042..f403f4ea 100644
--- a/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
+++ b/tests/auto/qbluetoothdevicediscoveryagent/tst_qbluetoothdevicediscoveryagent.cpp
@@ -597,7 +597,9 @@ void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods()
const QBluetoothDeviceInfo info =
qvariant_cast<QBluetoothDeviceInfo>(discoveredSpy.takeFirst().at(0));
QVERIFY(info.isValid());
- QVERIFY(info.coreConfigurations() & expectedConfiguration);
+ // on Android we do find devices with unknown configuration
+ if (info.coreConfigurations() != QBluetoothDeviceInfo::UnknownCoreConfiguration)
+ QVERIFY(info.coreConfigurations() & expectedConfiguration);
}
}
diff --git a/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro b/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro
index 7d4eba6f..4fd115bf 100644
--- a/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro
+++ b/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro
@@ -2,6 +2,8 @@ SOURCES += tst_qbluetoothservicediscoveryagent.cpp
TARGET = tst_qbluetoothservicediscoveryagent
CONFIG += testcase
+macos: QMAKE_INFO_PLIST = ../shared/Info.macos.plist
+
QT = core concurrent bluetooth testlib
osx:QT += widgets
diff --git a/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp b/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp
index 94a065bc..52a0c8bd 100644
--- a/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp
+++ b/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp
@@ -64,6 +64,7 @@ private slots:
void tst_invalidBtAddress();
void tst_serviceDiscovery_data();
void tst_serviceDiscovery();
+ void tst_serviceDiscoveryStop();
void tst_serviceDiscoveryAdapters();
private:
@@ -141,6 +142,28 @@ void tst_QBluetoothServiceDiscoveryAgent::initTestCase()
}
}
+void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscoveryStop()
+{
+ if (!localDeviceAvailable)
+ QSKIP("This test requires Bluetooth adapter in powered ON state");
+
+ QBluetoothServiceDiscoveryAgent discoveryAgent;
+ QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
+ QSignalSpy canceledSpy(&discoveryAgent, SIGNAL(canceled()));
+
+ // Verify we get the correct signals on start-stop
+ discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
+ QVERIFY(discoveryAgent.isActive());
+ discoveryAgent.stop();
+ QTRY_COMPARE(canceledSpy.count(), 1);
+ QVERIFY(!discoveryAgent.isActive());
+ // Wait a bit to see that there are no latent signals
+ QTest::qWait(200);
+ QCOMPARE(canceledSpy.count(), 1);
+ QCOMPARE(finishedSpy.count(), 0);
+}
+
+
void tst_QBluetoothServiceDiscoveryAgent::tst_invalidBtAddress()
{
#ifdef Q_OS_OSX
diff --git a/tests/auto/qbluetoothserviceinfo/tst_qbluetoothserviceinfo.cpp b/tests/auto/qbluetoothserviceinfo/tst_qbluetoothserviceinfo.cpp
index f89802d2..895529c7 100644
--- a/tests/auto/qbluetoothserviceinfo/tst_qbluetoothserviceinfo.cpp
+++ b/tests/auto/qbluetoothserviceinfo/tst_qbluetoothserviceinfo.cpp
@@ -38,6 +38,8 @@
#include <qbluetoothuuid.h>
#include <QtBluetooth/QBluetoothServer>
+#include <QtCore/qoperatingsystemversion.h>
+
QT_USE_NAMESPACE
Q_DECLARE_METATYPE(QBluetoothUuid::ProtocolUuid)
@@ -187,6 +189,11 @@ void tst_QBluetoothServiceInfo::tst_assignment_data()
#if defined(QT_ANDROID_BLUETOOTH) || defined(Q_OS_WIN)
l2cpSupported = false;
#endif
+
+#if defined(Q_OS_MACOS)
+ l2cpSupported = QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSBigSur;
+#endif
+
QTest::newRow("assignment_data_l2cp")
<< QUuid(0x67c8770b, 0x44f1, 0x410a, 0xab, 0x9a, 0xf9, 0xb5, 0x44, 0x6f, 0x13, 0xee)
<< QBluetoothUuid::L2cap << QBluetoothServiceInfo::L2capProtocol << l2cpSupported;
@@ -348,6 +355,19 @@ void tst_QBluetoothServiceInfo::tst_assignment()
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList,
protocolDescriptorList);
+#if defined(Q_OS_MACOS)
+ // bluetoothd on Monterey does not want to register a record if there is no
+ // ServiceClassIDList provided.
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) {
+ // Nothing seems to help with L2CAP though:
+ if (serviceInfoProtocol == QBluetoothServiceInfo::RfcommProtocol) {
+ QBluetoothServiceInfo::Sequence classIds;
+ classIds << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::SerialPort));
+ copyInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classIds);
+ }
+ }
+#endif // Q_OS_MACOS
+
QVERIFY(copyInfo.registerService());
QVERIFY(copyInfo.isRegistered());
QVERIFY(serviceInfo.isRegistered());
diff --git a/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp b/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
index 05bc1a0f..9e0dfc54 100644
--- a/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
+++ b/tests/auto/qbluetoothsocket/tst_qbluetoothsocket.cpp
@@ -35,6 +35,9 @@
#include <qbluetoothserviceinfo.h>
#include <qbluetoothservicediscoveryagent.h>
#include <qbluetoothlocaldevice.h>
+#if QT_CONFIG(bluez)
+#include <QtBluetooth/private/bluez5_helper_p.h>
+#endif
QT_USE_NAMESPACE
@@ -142,7 +145,7 @@ void tst_QBluetoothSocket::initTestCase()
qDebug() << "Starting discovery";
sda->setUuidFilter(QBluetoothUuid(QString(TEST_SERVICE_UUID)));
- sda->start(QBluetoothServiceDiscoveryAgent::MinimalDiscovery);
+ sda->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
for (int connectTime = MaxConnectTime; !done_discovery && connectTime > 0; connectTime -= 1000)
QTest::qWait(1000);
@@ -506,7 +509,12 @@ void tst_QBluetoothSocket::tst_preferredSecurityFlags()
#if defined(QT_ANDROID_BLUETOOTH) | defined(QT_OSX_BLUETOOTH)
QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Secure);
#elif QT_CONFIG(bluez)
- QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Authorization);
+ // The bluezdbus socket uses "NoSecurity" by default, whereas the non-dbus bluez
+ // socket uses "Authorization" by default
+ if (bluetoothdVersion() >= QVersionNumber(5, 42))
+ QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Security::NoSecurity);
+ else
+ QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::Security::Authorization);
#else
QCOMPARE(socket.preferredSecurityFlags(), QBluetooth::NoSecurity);
#endif
diff --git a/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro b/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro
index 24106573..98562724 100644
--- a/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro
+++ b/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro
@@ -2,6 +2,8 @@ SOURCES += tst_qlowenergycharacteristic.cpp
TARGET = tst_qlowenergycharacteristic
CONFIG += testcase
+macos: QMAKE_INFO_PLIST = ../shared/Info.macos.plist
+
QT = core bluetooth testlib
diff --git a/tests/auto/qlowenergycontroller/qlowenergycontroller.pro b/tests/auto/qlowenergycontroller/qlowenergycontroller.pro
index 7a67e8e4..a3d67ec9 100644
--- a/tests/auto/qlowenergycontroller/qlowenergycontroller.pro
+++ b/tests/auto/qlowenergycontroller/qlowenergycontroller.pro
@@ -8,6 +8,8 @@ CONFIG += testcase
SOURCES += tst_qlowenergycontroller.cpp
+macos: QMAKE_INFO_PLIST = ../shared/Info.macos.plist
+
osx|ios {
QT += widgets
}
diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
index bc74c693..08590a31 100644
--- a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
+++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp
@@ -176,7 +176,8 @@ void tst_QLowEnergyController::initTestCase()
}
}
- QVERIFY2(deviceFound, "Cannot find remote device.");
+ if (!deviceFound)
+ qWarning() << "Unable to find the TI sensor tag device, will skip most of the test";
// These are the services exported by the TI SensorTag
#ifndef Q_OS_MAC
diff --git a/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro b/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro
index 81ec9566..00ebe5c2 100644
--- a/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro
+++ b/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro
@@ -2,5 +2,7 @@ SOURCES += tst_qlowenergydescriptor.cpp
TARGET = tst_qlowenergydescriptor
CONFIG += testcase
+macos: QMAKE_INFO_PLIST = ../shared/Info.macos.plist
+
QT = core bluetooth testlib
diff --git a/tests/auto/shared/Info.macos.plist b/tests/auto/shared/Info.macos.plist
new file mode 100644
index 00000000..e447b77c
--- /dev/null
+++ b/tests/auto/shared/Info.macos.plist
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSBluetoothAlwaysUsageDescription</key>
+ <string>Qt BT test wants to access your Bluetooth adapter</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>