diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java | 434 | ||||
-rw-r--r-- | src/android/nfc/src/org/qtproject/qt5/android/nfc/QtNfc.java | 8 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtconnectionmonitor.mm | 24 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtconnectionmonitor_p.h | 2 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility.mm | 19 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility_p.h | 6 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm | 21 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_osx.mm | 6 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_winrt.cpp | 46 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_darwin.mm | 65 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_darwin_p.h | 5 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt.cpp | 7 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt_new.cpp | 10 |
13 files changed, 459 insertions, 194 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java index 9ef254f5..2e3e32d5 100644 --- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java +++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java @@ -82,11 +82,20 @@ public class QtBluetoothLEServer { private BluetoothGattServer mGattServer = null; private BluetoothLeAdvertiser mLeAdvertiser = null; + private ArrayList<BluetoothGattService> mPendingServiceAdditions = + new ArrayList<BluetoothGattService>(); + private String mRemoteName = ""; - public String remoteName() { return mRemoteName; } + // This function is called from Qt thread + public synchronized String remoteName() { + return mRemoteName; + } private String mRemoteAddress = ""; - public String remoteAddress() { return mRemoteAddress; } + // This function is called from Qt thread + public synchronized String remoteAddress() { + return mRemoteAddress; + } /* As per Bluetooth specification each connected device can have individual and persistent @@ -221,170 +230,297 @@ public class QtBluetoothLEServer { Log.w(TAG, "Let's do BTLE Peripheral."); } - /* - * Call back handler for the Gatt Server. - */ - private BluetoothGattServerCallback mGattServerListener = new BluetoothGattServerCallback() - { - @Override - public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { - Log.w(TAG, "Our gatt server connection state changed, new state: " + newState + " " + status); - super.onConnectionStateChange(device, status, newState); - int qtControllerState = 0; - switch (newState) { - case BluetoothProfile.STATE_DISCONNECTED: - qtControllerState = 0; // QLowEnergyController::UnconnectedState - clientCharacteristicManager.markDeviceConnectivity(device, false); - mGattServer.close(); - mGattServer = null; - break; - case BluetoothProfile.STATE_CONNECTED: - clientCharacteristicManager.markDeviceConnectivity(device, true); - qtControllerState = 2; // QLowEnergyController::ConnectedState - break; - } + // The following functions are synchronized callback handlers. The callbacks + // from Android are forwarded to these methods to synchronize member variable + // access with other threads (the Qt thread's JNI calls in particular). + // + // We use a single lock object (this server) for simplicity because: + // - Some variables may change and would thus not be suitable as locking objects but + // would require their own additional objects => overhead + // - Many accesses to shared variables are infrequent and the code paths are fast and + // deterministic meaning that long "wait times" on a lock should not happen + // - Typically several shared variables are accessed in a single code block. + // If each variable would be protected individually, the amount of (nested) locking + // would become quite unreasonable + + public synchronized void handleOnConnectionStateChange(BluetoothDevice device, + int status, int newState) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring connection state event, server is disconnected"); + return; + } - mRemoteName = device.getName(); - mRemoteAddress = device.getAddress(); + int qtControllerState = 0; + switch (newState) { + case BluetoothProfile.STATE_DISCONNECTED: + qtControllerState = 0; // QLowEnergyController::UnconnectedState + clientCharacteristicManager.markDeviceConnectivity(device, false); + mGattServer.close(); + mPendingServiceAdditions.clear(); + mGattServer = null; + break; + case BluetoothProfile.STATE_CONNECTED: + clientCharacteristicManager.markDeviceConnectivity(device, true); + qtControllerState = 2; // QLowEnergyController::ConnectedState + break; + } - int qtErrorCode; - switch (status) { - case BluetoothGatt.GATT_SUCCESS: - qtErrorCode = 0; break; - default: - Log.w(TAG, "Unhandled error code on peripheral connectionStateChanged: " + status + " " + newState); - qtErrorCode = status; - break; - } + mRemoteName = device.getName(); + mRemoteAddress = device.getAddress(); - leServerConnectionStateChange(qtObject, qtErrorCode, qtControllerState); + int qtErrorCode; + switch (status) { + case BluetoothGatt.GATT_SUCCESS: + qtErrorCode = 0; + break; + default: + Log.w(TAG, "Unhandled error code on peripheral connectionStateChanged: " + + status + " " + newState); + qtErrorCode = status; + break; } + leServerConnectionStateChange(qtObject, qtErrorCode, qtControllerState); + } - @Override - public void onServiceAdded(int status, BluetoothGattService service) { - super.onServiceAdded(status, service); + public synchronized void handleOnServiceAdded(int status, BluetoothGattService service) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring service addition event, server is disconnected"); + return; } - @Override - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) - { - byte[] dataArray; - try { - dataArray = Arrays.copyOfRange(characteristic.getValue(), offset, characteristic.getValue().length); - mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, dataArray); - } catch (Exception ex) { - Log.w(TAG, "onCharacteristicReadRequest: " + requestId + " " + offset + " " + characteristic.getValue().length); - ex.printStackTrace(); - mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null); - } + Log.d(TAG, "Service " + service.getUuid().toString() + " addition result: " + status); - super.onCharacteristicReadRequest(device, requestId, offset, characteristic); + // Remove the indicated service from the pending queue + ListIterator<BluetoothGattService> iterator = mPendingServiceAdditions.listIterator(); + while (iterator.hasNext()) { + if (iterator.next().getUuid().equals(service.getUuid())) { + iterator.remove(); + break; + } } - @Override - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, - boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) - { - Log.w(TAG, "onCharacteristicWriteRequest"); - int resultStatus = BluetoothGatt.GATT_SUCCESS; - boolean sendNotificationOrIndication = false; - if (!preparedWrite) { // regular write - if (offset == 0) { - characteristic.setValue(value); - leServerCharacteristicChanged(qtObject, characteristic, value); - sendNotificationOrIndication = true; - } else { - // This should not really happen as per Bluetooth spec - Log.w(TAG, "onCharacteristicWriteRequest: !preparedWrite, offset " + offset + ", Not supported"); - resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; - } + // If there are more services in the queue, add the next whose add initiation succeeds + iterator = mPendingServiceAdditions.listIterator(); + while (iterator.hasNext()) { + BluetoothGattService nextService = iterator.next(); + if (mGattServer.addService(nextService)) { + break; + } else { + Log.w(TAG, "Adding service " + nextService.getUuid().toString() + " failed"); + iterator.remove(); + } + } + } + public synchronized void handleOnCharacteristicReadRequest(BluetoothDevice device, + int requestId, int offset, + BluetoothGattCharacteristic characteristic) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring characteristic read, server is disconnected"); + return; + } + byte[] dataArray; + try { + dataArray = Arrays.copyOfRange(characteristic.getValue(), + offset, characteristic.getValue().length); + mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, + offset, dataArray); + } catch (Exception ex) { + Log.w(TAG, "onCharacteristicReadRequest: " + requestId + " " + + offset + " " + characteristic.getValue().length); + ex.printStackTrace(); + mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null); + } + } + public synchronized void handleOnCharacteristicWriteRequest(BluetoothDevice device, + int requestId, + BluetoothGattCharacteristic characteristic, + boolean preparedWrite, boolean responseNeeded, + int offset, byte[] value) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring characteristic write, server is disconnected"); + return; + } + Log.w(TAG, "onCharacteristicWriteRequest"); + int resultStatus = BluetoothGatt.GATT_SUCCESS; + boolean sendNotificationOrIndication = false; + if (!preparedWrite) { // regular write + if (offset == 0) { + characteristic.setValue(value); + leServerCharacteristicChanged(qtObject, characteristic, value); + sendNotificationOrIndication = true; } else { - Log.w(TAG, "onCharacteristicWriteRequest: preparedWrite, offset " + offset + ", Not supported"); + // This should not really happen as per Bluetooth spec + Log.w(TAG, "onCharacteristicWriteRequest: !preparedWrite, offset " + + offset + ", Not supported"); resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; - - // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received - // we use a queue to remember the pending requests - // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device } + } else { + Log.w(TAG, "onCharacteristicWriteRequest: preparedWrite, offset " + + offset + ", Not supported"); + resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; + + // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received + // we use a queue to remember the pending requests + // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device + } - if (responseNeeded) - mGattServer.sendResponse(device, requestId, resultStatus, offset, value); - if (sendNotificationOrIndication) - sendNotificationsOrIndications(characteristic); + if (responseNeeded) + mGattServer.sendResponse(device, requestId, resultStatus, offset, value); + if (sendNotificationOrIndication) + sendNotificationsOrIndications(characteristic); + } - super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); + public synchronized void handleOnDescriptorReadRequest(BluetoothDevice device, int requestId, + int offset, BluetoothGattDescriptor descriptor) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring descriptor read, server is disconnected"); + return; } - @Override - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) - { - byte[] dataArray = descriptor.getValue(); - try { - if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) { - dataArray = clientCharacteristicManager.valueFor(descriptor.getCharacteristic(), device); - if (dataArray == null) - dataArray = descriptor.getValue(); - } - - dataArray = Arrays.copyOfRange(dataArray, offset, dataArray.length); - mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, dataArray); - } catch (Exception ex) { - Log.w(TAG, "onDescriptorReadRequest: " + requestId + " " + offset + " " + dataArray.length); - ex.printStackTrace(); - mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null); + byte[] dataArray = descriptor.getValue(); + try { + if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) { + dataArray = clientCharacteristicManager.valueFor( + descriptor.getCharacteristic(), device); + if (dataArray == null) + dataArray = descriptor.getValue(); } - super.onDescriptorReadRequest(device, requestId, offset, descriptor); + dataArray = Arrays.copyOfRange(dataArray, offset, dataArray.length); + mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, + offset, dataArray); + } catch (Exception ex) { + Log.w(TAG, "onDescriptorReadRequest: " + requestId + " " + + offset + " " + dataArray.length); + ex.printStackTrace(); + mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, + offset, null); } + } - @Override - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, - boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) - { - int resultStatus = BluetoothGatt.GATT_SUCCESS; - if (!preparedWrite) { // regular write - if (offset == 0) { - descriptor.setValue(value); - - if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) { - clientCharacteristicManager.insertOrUpdate(descriptor.getCharacteristic(), - device, value); - } + public synchronized void handleOnDescriptorWriteRequest(BluetoothDevice device, int requestId, + BluetoothGattDescriptor descriptor, boolean preparedWrite, + boolean responseNeeded, int offset, byte[] value) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring descriptor write, server is disconnected"); + return; + } + int resultStatus = BluetoothGatt.GATT_SUCCESS; + if (!preparedWrite) { // regular write + if (offset == 0) { + descriptor.setValue(value); - leServerDescriptorWritten(qtObject, descriptor, value); - } else { - // This should not really happen as per Bluetooth spec - Log.w(TAG, "onDescriptorWriteRequest: !preparedWrite, offset " + offset + ", Not supported"); - resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; + if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) { + clientCharacteristicManager.insertOrUpdate(descriptor.getCharacteristic(), + device, value); } - + leServerDescriptorWritten(qtObject, descriptor, value); } else { - Log.w(TAG, "onDescriptorWriteRequest: preparedWrite, offset " + offset + ", Not supported"); + // This should not really happen as per Bluetooth spec + Log.w(TAG, "onDescriptorWriteRequest: !preparedWrite, offset " + + offset + ", Not supported"); resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; - // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received - // we use a queue to remember the pending requests - // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device } + } else { + Log.w(TAG, "onDescriptorWriteRequest: preparedWrite, offset " + + offset + ", Not supported"); + resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED; + // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received + // we use a queue to remember the pending requests + // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device + } - if (responseNeeded) - mGattServer.sendResponse(device, requestId, resultStatus, offset, value); + if (responseNeeded) + mGattServer.sendResponse(device, requestId, resultStatus, offset, value); + } - super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); + public synchronized void handleOnExecuteWrite(BluetoothDevice device, + int requestId, boolean execute) + { + if (mGattServer == null) { + Log.w(TAG, "Ignoring execute write, server is disconnected"); + return; } + // TODO not yet implemented -> return proper GATT error for it + mGattServer.sendResponse(device, requestId, + BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null); + } + /* + * Call back handler for the Gatt Server. + */ + private BluetoothGattServerCallback mGattServerListener = new BluetoothGattServerCallback() + { @Override - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) + public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { + super.onConnectionStateChange(device, status, newState); + handleOnConnectionStateChange(device, status, newState); + } + + @Override + public void onServiceAdded(int status, BluetoothGattService service) { + super.onServiceAdded(status, service); + handleOnServiceAdded(status, service); + } + + @Override + public void onCharacteristicReadRequest(BluetoothDevice device, + int requestId, int offset, + BluetoothGattCharacteristic characteristic) + { + super.onCharacteristicReadRequest(device, requestId, offset, characteristic); + handleOnCharacteristicReadRequest(device, requestId, offset, characteristic); + } + + @Override + public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, + BluetoothGattCharacteristic characteristic, + boolean preparedWrite, boolean responseNeeded, + int offset, byte[] value) + { + super.onCharacteristicWriteRequest(device, requestId, characteristic, + preparedWrite, responseNeeded, offset, value); + handleOnCharacteristicWriteRequest(device, requestId, characteristic, + preparedWrite, responseNeeded, offset, value); + } + + @Override + public void onDescriptorReadRequest(BluetoothDevice device, int requestId, + int offset, BluetoothGattDescriptor descriptor) + { + super.onDescriptorReadRequest(device, requestId, offset, descriptor); + handleOnDescriptorReadRequest(device, requestId, offset, descriptor); + } + + @Override + public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, + BluetoothGattDescriptor descriptor, + boolean preparedWrite, boolean responseNeeded, + int offset, byte[] value) { - // TODO not yet implemented -> return proper GATT error for it - mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null); + super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, + responseNeeded, offset, value); + handleOnDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, + responseNeeded, offset, value); + } + @Override + public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) + { super.onExecuteWrite(device, requestId, execute); + handleOnExecuteWrite(device, requestId, execute); } @Override @@ -393,14 +529,15 @@ public class QtBluetoothLEServer { Log.w(TAG, "onNotificationSent" + device + " " + status); } - // MTU change disabled since it requires API level 22. Right now we only enforce lvl 21 +// MTU change disabled since it requires API level 22. Right now we only enforce lvl 21 // @Override // public void onMtuChanged(BluetoothDevice device, int mtu) { // super.onMtuChanged(device, mtu); // } }; - public boolean connectServer() + // This function is called from Qt thread + public synchronized boolean connectServer() { if (mGattServer != null) return true; @@ -416,11 +553,13 @@ public class QtBluetoothLEServer { return (mGattServer != null); } - public void disconnectServer() + // This function is called from Qt thread + public synchronized void disconnectServer() { if (mGattServer == null) return; + mPendingServiceAdditions.clear(); mGattServer.close(); mGattServer = null; @@ -428,6 +567,7 @@ public class QtBluetoothLEServer { leServerConnectionStateChange(qtObject, 0 /*NoError*/, 0 /*QLowEnergyController::UnconnectedState*/); } + // This function is called from Qt thread public boolean startAdvertising(AdvertiseData advertiseData, AdvertiseData scanResponse, AdvertiseSettings settings) @@ -446,6 +586,7 @@ public class QtBluetoothLEServer { return true; } + // This function is called from Qt thread public void stopAdvertising() { if (mLeAdvertiser == null) @@ -455,20 +596,33 @@ public class QtBluetoothLEServer { Log.w(TAG, "Advertisement stopped."); } - public void addService(BluetoothGattService service) + // This function is called from Qt thread + public synchronized void addService(BluetoothGattService service) { if (!connectServer()) { Log.w(TAG, "Server::addService: Cannot open GATT server"); return; } - boolean success = mGattServer.addService(service); - Log.w(TAG, "Services successfully added: " + success); + // When we add a service, we must wait for onServiceAdded callback before adding the + // next one. If the pending service queue is empty it means that there are no ongoing + // service additions => add the service to the server. If there are services in the + // queue it means there is an initiated addition ongoing, and we only add to the queue. + if (mPendingServiceAdditions.isEmpty()) { + if (mGattServer.addService(service)) + mPendingServiceAdditions.add(service); + else + Log.w(TAG, "Adding service " + service.getUuid().toString() + " failed."); + } else { + mPendingServiceAdditions.add(service); + } } /* Check the client characteristics configuration for the given characteristic and sends notifications or indications as per required. + + This function is called from Qt and Java threads and calls must be protected */ private void sendNotificationsOrIndications(BluetoothGattCharacteristic characteristic) { @@ -518,9 +672,13 @@ public class QtBluetoothLEServer { return false; } - foundChar.setValue(newValue); - sendNotificationsOrIndications(foundChar); - + synchronized (this) // a value update might be in progress + { + foundChar.setValue(newValue); + // Value is updated even if server is not connected, but notifying is not possible + if (mGattServer != null) + sendNotificationsOrIndications(foundChar); + } return true; } @@ -557,8 +715,10 @@ public class QtBluetoothLEServer { // we even write CLIENT_CHARACTERISTIC_CONFIGURATION_UUID this way as we choose // to interpret the server's call as a change of the default value. - foundDesc.setValue(newValue); - + synchronized (this) // a value update might be in progress + { + foundDesc.setValue(newValue); + } return true; } diff --git a/src/android/nfc/src/org/qtproject/qt5/android/nfc/QtNfc.java b/src/android/nfc/src/org/qtproject/qt5/android/nfc/QtNfc.java index 19e645f5..610d4cd6 100644 --- a/src/android/nfc/src/org/qtproject/qt5/android/nfc/QtNfc.java +++ b/src/android/nfc/src/org/qtproject/qt5/android/nfc/QtNfc.java @@ -52,6 +52,7 @@ import android.content.IntentFilter; import android.nfc.NfcAdapter; import android.content.IntentFilter.MalformedMimeTypeException; import android.os.Bundle; +import android.os.Build; import android.util.Log; import android.content.BroadcastReceiver; import android.content.pm.PackageManager; @@ -82,11 +83,16 @@ public class QtNfc return; } + // Since Android 12 (API level 31) it's mandatory to specify mutability + // of PendingIntent. We need a mutable intent, which was a default + // option earlier. + int flags = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? PendingIntent.FLAG_MUTABLE + : 0; m_pendingIntent = PendingIntent.getActivity( m_activity, 0, new Intent(m_activity, m_activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), - 0); + flags); //Log.d(TAG, "Pending intent:" + m_pendingIntent); diff --git a/src/bluetooth/osx/osxbtconnectionmonitor.mm b/src/bluetooth/osx/osxbtconnectionmonitor.mm index b777af8e..f41bbed5 100644 --- a/src/bluetooth/osx/osxbtconnectionmonitor.mm +++ b/src/bluetooth/osx/osxbtconnectionmonitor.mm @@ -81,14 +81,8 @@ using namespace QT_NAMESPACE; - (void)dealloc { - [discoveryNotification unregister]; - [discoveryNotification release]; - - for (IOBluetoothUserNotification *n in foundConnections) - [n unregister]; - - [foundConnections release]; - + Q_ASSERT_X(!monitor, "-dealloc", + "Connection monitor was not stopped, calling -stopMonitoring is required"); [super dealloc]; } @@ -137,4 +131,18 @@ using namespace QT_NAMESPACE; monitor->deviceDisconnected(deviceAddress); } +- (void)stopMonitoring +{ + monitor = nullptr; + [discoveryNotification unregister]; + [discoveryNotification release]; + discoveryNotification = nil; + + for (IOBluetoothUserNotification *n in foundConnections) + [n unregister]; + + [foundConnections release]; + foundConnections = nil; +} + @end diff --git a/src/bluetooth/osx/osxbtconnectionmonitor_p.h b/src/bluetooth/osx/osxbtconnectionmonitor_p.h index 679f6124..50dc9d77 100644 --- a/src/bluetooth/osx/osxbtconnectionmonitor_p.h +++ b/src/bluetooth/osx/osxbtconnectionmonitor_p.h @@ -84,6 +84,8 @@ QT_END_NAMESPACE - (void)connectionNotification:(id)notification withDevice:(IOBluetoothDevice *)device; - (void)connectionClosedNotification:(id)notification withDevice:(IOBluetoothDevice *)device; +- (void)stopMonitoring; + @end #endif diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm index c7fa7c42..3d41a224 100644 --- a/src/bluetooth/osx/osxbtutility.mm +++ b/src/bluetooth/osx/osxbtutility.mm @@ -42,6 +42,7 @@ #include "osxbtutility_p.h" #include "qbluetoothuuid.h" +#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qendian.h> #include <QtCore/qstring.h> @@ -76,6 +77,8 @@ const int defaultLEScanTimeoutMS = 25000; // We use it only on iOS for now: const int maxValueLength = 512; +NSString *const bluetoothUsageKey = @"NSBluetoothAlwaysUsageDescription"; + QString qt_address(NSString *address) { if (address && address.length) { @@ -351,6 +354,22 @@ ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray return result; } +bool qt_appNeedsBluetoothUsageDescription() +{ +#ifdef Q_OS_MACOS + return QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur; +#endif + return true; +} + +bool qt_appPlistContainsDescription(NSString *key) +{ + Q_ASSERT(key); + + NSDictionary<NSString *, id> *infoDict = NSBundle.mainBundle.infoDictionary; + return !!infoDict[key]; +} + // A small RAII class for a dispatch queue. class SerialDispatchQueue { diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h index c2bc6cf8..1b1d44be 100644 --- a/src/bluetooth/osx/osxbtutility_p.h +++ b/src/bluetooth/osx/osxbtutility_p.h @@ -307,6 +307,12 @@ dispatch_queue_t qt_LE_queue(); extern const int defaultLEScanTimeoutMS; extern const int maxValueLength; +// Add more keys if needed, for now this one is enough: +extern NSString *const bluetoothUsageKey; + +bool qt_appNeedsBluetoothUsageDescription(); +bool qt_appPlistContainsDescription(NSString *key); + } // namespace OSXBluetooth // Logging category for both OS X and iOS. diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm index d9883d28..a5cb034a 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_darwin.mm @@ -144,6 +144,8 @@ bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) { + using namespace OSXBluetooth; + Q_ASSERT(!isActive()); Q_ASSERT(lastError != QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); Q_ASSERT(methods & (QBluetoothDeviceDiscoveryAgent::ClassicMethod @@ -157,6 +159,25 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent } #endif // Q_OS_MACOS + // To be able to scan for devices, iOS requires Info.plist containing + // NSBluetoothAlwaysUsageDescription entry with a string, explaining + // the usage of Bluetooth interface. macOS also requires this description, + // starting from Monterey. + + // No Classic on iOS, and Classic does not require a description on macOS: + if (methods.testFlag(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + && qt_appNeedsBluetoothUsageDescription() + && !qt_appPlistContainsDescription(bluetoothUsageKey)) { + // This would result in Bluetooth framework throwing an exception + // the moment we try to start device discovery. + qCWarning(QT_BT_OSX) + << "A proper Info.plist with NSBluetoothAlwaysUsageDescription " + "entry is required, cannot start device discovery"; + setError(QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); + emit q_ptr->error(lastError); + return; + } + requestedMethods = methods; if (stopPending) { diff --git a/src/bluetooth/qbluetoothlocaldevice_osx.mm b/src/bluetooth/qbluetoothlocaldevice_osx.mm index e7dd9906..2bf467f8 100644 --- a/src/bluetooth/qbluetoothlocaldevice_osx.mm +++ b/src/bluetooth/qbluetoothlocaldevice_osx.mm @@ -66,6 +66,7 @@ public: QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *, const QBluetoothAddress & = QBluetoothAddress()); + ~QBluetoothLocalDevicePrivate(); bool isValid() const; void requestPairing(const QBluetoothAddress &address, Pairing pairing); @@ -147,6 +148,11 @@ QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice connectionMonitor.reset([[ObjCConnectionMonitor alloc] initWithMonitor:this]); } +QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate() +{ + [connectionMonitor stopMonitoring]; +} + bool QBluetoothLocalDevicePrivate::isValid() const { return hostController.data(); diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp index 48b14757..d562f6f7 100644 --- a/src/bluetooth/qbluetoothsocket_winrt.cpp +++ b/src/bluetooth/qbluetoothsocket_winrt.cpp @@ -576,6 +576,17 @@ QString QBluetoothSocketPrivateWinRT::localName() const return device.name(); } +static QString fromWinApiAddress(HString address) +{ + // WinAPI returns address with parentheses around it. We need to remove + // them to convert to QBluetoothAddress. + QString addressStr(qt_QStringFromHString(address)); + if (addressStr.startsWith(QLatin1Char('(')) && addressStr.endsWith(QLatin1Char(')'))) { + addressStr = addressStr.mid(1, addressStr.size() - 2); + } + return addressStr; +} + QBluetoothAddress QBluetoothSocketPrivateWinRT::localAddress() const { if (!m_socketObject) @@ -588,10 +599,13 @@ QBluetoothAddress QBluetoothSocketPrivateWinRT::localAddress() const ComPtr<IHostName> localHost; hr = info->get_LocalAddress(&localHost); Q_ASSERT_SUCCEEDED(hr); - HString localAddress; - hr = localHost->get_CanonicalName(localAddress.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - return QBluetoothAddress(qt_QStringFromHString(localAddress)); + if (localHost) { + HString localAddress; + hr = localHost->get_CanonicalName(localAddress.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + return QBluetoothAddress(fromWinApiAddress(std::move(localAddress))); + } + return QBluetoothAddress(); } quint16 QBluetoothSocketPrivateWinRT::localPort() const @@ -627,10 +641,13 @@ QString QBluetoothSocketPrivateWinRT::peerName() const ComPtr<IHostName> remoteHost; hr = info->get_RemoteHostName(&remoteHost); Q_ASSERT_SUCCEEDED(hr); - HString remoteHostName; - hr = remoteHost->get_DisplayName(remoteHostName.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - return qt_QStringFromHString(remoteHostName); + if (remoteHost) { + HString remoteHostName; + hr = remoteHost->get_DisplayName(remoteHostName.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + return qt_QStringFromHString(remoteHostName); + } + return {}; } QBluetoothAddress QBluetoothSocketPrivateWinRT::peerAddress() const @@ -645,10 +662,13 @@ QBluetoothAddress QBluetoothSocketPrivateWinRT::peerAddress() const ComPtr<IHostName> remoteHost; hr = info->get_RemoteAddress(&remoteHost); Q_ASSERT_SUCCEEDED(hr); - HString remoteAddress; - hr = remoteHost->get_CanonicalName(remoteAddress.GetAddressOf()); - Q_ASSERT_SUCCEEDED(hr); - return QBluetoothAddress(qt_QStringFromHString(remoteAddress)); + if (remoteHost) { + HString remoteAddress; + hr = remoteHost->get_CanonicalName(remoteAddress.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + return QBluetoothAddress(fromWinApiAddress(std::move(remoteAddress))); + } + return QBluetoothAddress(); } quint16 QBluetoothSocketPrivateWinRT::peerPort() const @@ -661,7 +681,7 @@ quint16 QBluetoothSocketPrivateWinRT::peerPort() const hr = m_socketObject->get_Information(&info); Q_ASSERT_SUCCEEDED(hr); HString remotePortString; - hr = info->get_LocalPort(remotePortString.GetAddressOf()); + hr = info->get_RemotePort(remotePortString.GetAddressOf()); Q_ASSERT_SUCCEEDED(hr); bool ok = true; const uint port = qt_QStringFromHString(remotePortString).toUInt(&ok); diff --git a/src/bluetooth/qlowenergycontroller_darwin.mm b/src/bluetooth/qlowenergycontroller_darwin.mm index e2b3d618..019cabf8 100644 --- a/src/bluetooth/qlowenergycontroller_darwin.mm +++ b/src/bluetooth/qlowenergycontroller_darwin.mm @@ -165,7 +165,14 @@ bool QLowEnergyControllerPrivateDarwin::isValid() const void QLowEnergyControllerPrivateDarwin::init() { - using OSXBluetooth::LECBManagerNotifier; + using namespace OSXBluetooth; + + if (qt_appNeedsBluetoothUsageDescription() && !qt_appPlistContainsDescription(bluetoothUsageKey)) { + qCWarning(QT_BT_OSX) + << "The Info.plist file is required to contain " + "'NSBluetoothAlwaysUsageDescription' entry"; + return; + } QScopedPointer<LECBManagerNotifier> notifier(new LECBManagerNotifier); if (role == QLowEnergyController::PeripheralRole) { @@ -202,7 +209,7 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice() Q_FUNC_INFO, "invalid state"); if (!isValid()) { - // init() had failed for was never called. + // init() had failed or was never called. return _q_CBManagerError(QLowEnergyController::UnknownError); } @@ -234,6 +241,8 @@ void QLowEnergyControllerPrivateDarwin::connectToDevice() void QLowEnergyControllerPrivateDarwin::disconnectFromDevice() { + Q_ASSERT(isValid()); // Check for proper state is in q's code. + if (role == QLowEnergyController::PeripheralRole) { // CoreBluetooth API intentionally does not provide any way of closing // a connection. All we can do here is to stop the advertisement. @@ -241,29 +250,28 @@ void QLowEnergyControllerPrivateDarwin::disconnectFromDevice() return; } - if (isValid()) { - const auto oldState = state; + const auto oldState = state; - if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { - setState(QLowEnergyController::ClosingState); - invalidateServices(); - auto manager = centralManager.getAs<ObjCCentralManager>(); - dispatch_async(leQueue, ^{ - [manager disconnectFromDevice]; - }); + if (dispatch_queue_t leQueue = OSXBluetooth::qt_LE_queue()) { + setState(QLowEnergyController::ClosingState); + invalidateServices(); - if (oldState == QLowEnergyController::ConnectingState) { - // With a pending connect attempt there is no - // guarantee we'll ever have didDisconnect callback, - // set the state here and now to make sure we still - // can connect. - setState(QLowEnergyController::UnconnectedState); - } - } else { - qCCritical(QT_BT_OSX) << "qt LE queue is nil, " - "can not dispatch 'disconnect'"; + auto manager = centralManager.getAs<ObjCCentralManager>(); + dispatch_async(leQueue, ^{ + [manager disconnectFromDevice]; + }); + + if (oldState == QLowEnergyController::ConnectingState) { + // With a pending connect attempt there is no + // guarantee we'll ever have didDisconnect callback, + // set the state here and now to make sure we still + // can connect. + setState(QLowEnergyController::UnconnectedState); } + } else { + qCCritical(QT_BT_OSX) << "qt LE queue is nil, " + "can not dispatch 'disconnect'"; } } @@ -274,6 +282,8 @@ void QLowEnergyControllerPrivateDarwin::discoverServices() Q_ASSERT_X(role != QLowEnergyController::PeripheralRole, Q_FUNC_INFO, "invalid role (peripheral)"); + Q_ASSERT(isValid()); // Check we're in a proper state is in q's code. + dispatch_queue_t leQueue(OSXBluetooth::qt_LE_queue()); Q_ASSERT_X(leQueue, Q_FUNC_INFO, "LE queue not found"); @@ -334,6 +344,11 @@ QLowEnergyService * QLowEnergyControllerPrivateDarwin::addServiceHelper(const QL Q_UNUSED(service); qCDebug(QT_BT_OSX, "peripheral role is not supported on tvOS"); #else + if (!isValid()) { + qCWarning(QT_BT_OSX) << "invalid peripheral"; + return nullptr; + } + if (role != QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "not in peripheral role"; return nullptr; @@ -1040,14 +1055,16 @@ void QLowEnergyControllerPrivateDarwin::startAdvertising(const QLowEnergyAdverti qCWarning(QT_BT_OSX) << "advertising is not supported on your platform"; #else - if (!isValid()) - return _q_CBManagerError(QLowEnergyController::UnknownError); - if (role != QLowEnergyController::PeripheralRole) { qCWarning(QT_BT_OSX) << "controller is not a peripheral, cannot start advertising"; return; } + if (!isValid()) { + qCWarning(QT_BT_OSX, "LE controller is an invalid peripheral"); + return; + } + if (state != QLowEnergyController::UnconnectedState) { qCWarning(QT_BT_OSX) << "invalid state" << state; return; diff --git a/src/bluetooth/qlowenergycontroller_darwin_p.h b/src/bluetooth/qlowenergycontroller_darwin_p.h index 960d7fbc..3c98ca34 100644 --- a/src/bluetooth/qlowenergycontroller_darwin_p.h +++ b/src/bluetooth/qlowenergycontroller_darwin_p.h @@ -108,7 +108,10 @@ public: const QLowEnergyAdvertisingData &scanResponseData) override; void stopAdvertising()override; QLowEnergyService *addServiceHelper(const QLowEnergyServiceData &service) override; - bool isValid() const; // QT6 - delete this logic. + + // Valid - a central or peripheral instance was allocated, and this may also + // mean a proper usage description was provided/found: + bool isValid() const; private Q_SLOTS: void _q_connected(); diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp index 5b14a92c..f69a0d91 100644 --- a/src/bluetooth/qlowenergycontroller_winrt.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt.cpp @@ -642,10 +642,10 @@ void QLowEnergyControllerPrivateWinRT::discoverServiceDetails(const QBluetoothUu QThread *thread = new QThread; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandler::obtainCharList); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater); - connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained, - [this, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList + connect(worker, &QObject::destroyed, thread, &QObject::deleteLater); + connect(worker, &QWinRTLowEnergyServiceHandler::charListObtained, this, + [this](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList , QVector<QBluetoothUuid> indicateChars , QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) { if (!serviceList.contains(service)) { @@ -668,7 +668,6 @@ void QLowEnergyControllerPrivateWinRT::discoverServiceDetails(const QBluetoothUu Q_ASSERT_SUCCEEDED(hr); pointer->setState(QLowEnergyService::ServiceDiscovered); - thread->exit(0); }); thread->start(); } diff --git a/src/bluetooth/qlowenergycontroller_winrt_new.cpp b/src/bluetooth/qlowenergycontroller_winrt_new.cpp index a6371c0a..42f4380e 100644 --- a/src/bluetooth/qlowenergycontroller_winrt_new.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt_new.cpp @@ -711,8 +711,8 @@ void QLowEnergyControllerPrivateWinRTNew::connectToDevice() connect(this, &QLowEnergyControllerPrivateWinRTNew::abortConnection, worker, &QWinRTLowEnergyConnectionHandler::handleDeviceDisconnectRequest); connect(thread, &QThread::started, worker, &QWinRTLowEnergyConnectionHandler::connectToDevice); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QObject::destroyed, thread, &QObject::deleteLater); connect(worker, &QWinRTLowEnergyConnectionHandler::errorOccurred, this, [this](const QString &msg) { handleConnectionError(msg.toUtf8().constData()); }); connect(worker, &QWinRTLowEnergyConnectionHandler::deviceConnected, this, @@ -1166,12 +1166,12 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot QThread *thread = new QThread; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &QWinRTLowEnergyServiceHandlerNew::obtainCharList); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &QObject::destroyed, thread, &QObject::deleteLater); connect(worker, &QWinRTLowEnergyServiceHandlerNew::errorOccured, this, &QLowEnergyControllerPrivateWinRTNew::handleServiceHandlerError); - connect(worker, &QWinRTLowEnergyServiceHandlerNew::charListObtained, - [this, reactOnDiscoveryError, thread](const QBluetoothUuid &service, QHash<QLowEnergyHandle, + connect(worker, &QWinRTLowEnergyServiceHandlerNew::charListObtained, this, + [this, reactOnDiscoveryError](const QBluetoothUuid &service, QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> charList, QVector<QBluetoothUuid> indicateChars, QLowEnergyHandle startHandle, QLowEnergyHandle endHandle) { if (!serviceList.contains(service)) { @@ -1194,12 +1194,10 @@ void QLowEnergyControllerPrivateWinRTNew::discoverServiceDetails(const QBluetoot if (FAILED(hr)) { reactOnDiscoveryError(pointer, QStringLiteral("Could not register for value changes in Xaml thread: %1").arg(hr)); - thread->exit(0); return; } pointer->setState(QLowEnergyService::ServiceDiscovered); - thread->exit(0); }); thread->start(); } |