diff options
author | Alex Blasche <alexander.blasche@qt.io> | 2016-12-14 11:08:32 +0100 |
---|---|---|
committer | Alex Blasche <alexander.blasche@qt.io> | 2016-12-14 11:10:37 +0100 |
commit | ce012f43185f43730c00e4ee43ae0085774fbb95 (patch) | |
tree | 262232557ee753190cb9a37336884e83e779b082 | |
parent | 1750983d3f053316c735d602c7f9a1b56dc4462b (diff) | |
parent | 1afb57ed92a25bbc000ea4c4d7662cb8b9e267ee (diff) |
Merge branch '5.8.0' into 5.8
Change-Id: Ic04b8dc00f754fb85f50209c822c2f3734fbf99e
-rw-r--r-- | dist/changes-5.8.0 | 76 | ||||
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java | 617 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_android.cpp | 56 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_android.cpp | 16 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 5 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_winrt.cpp | 3 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyservice.cpp | 21 | ||||
-rw-r--r-- | src/imports/bluetooth/plugin.cpp | 6 | ||||
-rw-r--r-- | src/imports/bluetooth/plugins.qmltypes | 6 | ||||
-rw-r--r-- | src/imports/nfc/plugin.cpp | 4 | ||||
-rw-r--r-- | src/imports/nfc/plugins.qmltypes | 6 | ||||
-rw-r--r-- | src/src.pro | 4 | ||||
-rw-r--r-- | src/tools/sdpscanner/qt_attribution.json | 2 |
13 files changed, 528 insertions, 294 deletions
diff --git a/dist/changes-5.8.0 b/dist/changes-5.8.0 new file mode 100644 index 00000000..4e013c13 --- /dev/null +++ b/dist/changes-5.8.0 @@ -0,0 +1,76 @@ +Qt 5.8 introduces many new features and improvements as well as bugfixes +over the 5.7.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.8 series is binary compatible with the 5.7.x series. +Applications compiled for 5.7 will continue to run with 5.8. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +QtBluetooth +----------- + + - Added Bluetooth Low Energy peripheral role support for iOS/macOS. + - Added WinRT support for QBluetoothDeviceDiscoveryAgent and Bluetooth Low + Energy central role. + - [QTBUG-53012] Added API to set the timeout of Bluetooth Low Energy device + discoveries. + - Fixed minor documentation issues. + - [QTBUG-46253] Added API to select the device discovery mode utilized by + QBluetoothDeviceDiscoveryAgent (btle vs classic vs combined). + - [QTBUG-46377] Added preferredSecurityGlags() and setPreferredSecurityFlags() + methods to QBluetoothSocket. + - Adjusted QtBluetooth to the changes required by the Qt Lite build system. + +QtNfc +----- + + - [QTBUG-55297] Fixed ODR violation in QLlcpServer on Android. + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Android +------- + + - [QTBUG-56625] Parsed list of advertised services found in LE scan + record and pass them to API client via QBluetoothDeviceInfo::serviceUuids() + - [QTBUG-55035] Added support for new runtime permission check when running Bluetooth + applications on Android 7.x. The Bluetooth stack requires Location permissions + when running a device discovery. + - [QTBUG-52692] Redesigned/Simplified Android central role implementation and prevented + blocking of service discovery in case the peripheral does not behave as per standard. + - [QTBUG-56625] Improved Low Energy device discovery by evaluating scan records and + feeding their content into QBluetoothDeviceInfo instances. + - [QTBUG-45066] Fixed crash in QBluetoothDeviceDiscoveryAgent ctor due to Java exception. + +iOS/macOS +-------- + + - Removed iOS v 6.x support from the code base + - [QTBUG-56898] Fixed a crash when writing GATT attributes. + - Adjusted code base to latest macOS and iOS releases. + - [QTBUG-53331] Forced error signal when calling + QBluetoothLocalDevice::requestPairing(). + - [QTBUG-52690] Added QLowEnergyController::remoteDeviceUuid() to expose the iOS/macOS + specific device UUID. Those two platforms do not expose Bluetooth addresses to + application developers. + +Linux/Bluez +----------- + + - Fixed build of sdpscanner due to incorrect build order + 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 9fe88e9c..747f46d0 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 @@ -48,12 +48,16 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.util.Log; +import java.util.concurrent.atomic.AtomicInteger; import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.UUID; public class QtBluetoothLE { @@ -65,6 +69,34 @@ public class QtBluetoothLE { private String mRemoteGattAddress; private final UUID clientCharacteristicUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + /* + * The atomic synchronizes the timeoutRunnable thread and the response thread for the pending + * I/O job. Whichever thread comes first will pass the atomic gate. The other thread is + * cut short. + */ + private AtomicInteger handleForTimeout = new AtomicInteger(-1); // -1 implies not running + private final int RUNNABLE_TIMEOUT = 3000; // 3 seconds + private final Handler timeoutHandler = new Handler(Looper.getMainLooper()); + + private class TimeoutRunnable implements Runnable { + public TimeoutRunnable(int handle) { pendingJobHandle = handle; } + @Override + public void run() { + boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, -1); + if (timeoutStillValid) { + Log.w(TAG, "****** Timeout for request on handle " + (pendingJobHandle & 0xffff)); + Log.w(TAG, "****** Looks like the characteristic or descriptor does NOT act in " + + "accordance to Bluetooth 4.x spec."); + Log.w(TAG, "****** Please check server implementation. Continuing under " + + "reservation."); + interruptCurrentIO(pendingJobHandle & 0xffff); + } + } + + // contains handle (0xffff) and top 2 byte contain the job type (0xffff0000) + private int pendingJobHandle = -1; + }; + /* Pointer to the Qt object that "owns" the Java object */ @SuppressWarnings({"CanBeFinal", "WeakerAccess"}) @@ -182,64 +214,67 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattCharacteristic characteristic, int status) { - //runningHandle is only used during serviceDetailsDiscovery - //If it is -1 we got an update outside of the details discovery process - final boolean isServiceDiscoveryRun = (runningHandle != -1); - - if (status != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "onCharacteristicRead error: " + 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) { + ioJobPending = false; + } - // read errors during serviceDiscovery are ignored - if (isServiceDiscoveryRun) + performNextIO(); return; + } } - synchronized (this) { - if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready; - return; + boolean requestTimedOut = !handleForTimeout.compareAndSet( + modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1); + 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 ioJobPending -> the timeout has done that already + return; } - // once we have a service discovery run we report regular changes - if (!isServiceDiscoveryRun) { + GattEntry entry = entries.get(foundHandle); + final boolean isServiceDiscoveryRun = !entry.valueKnown; + entry.valueKnown = true; - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForCharacteristic(characteristic); + 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) { + //do nothing just continue with queue + Log.w(TAG, "onCharacteristicRead during discovery error: " + status); + } else { + // This must be in sync with QLowEnergyService::CharacteristicReadError + final int characteristicReadError = 5; + leServiceError(qtObject, foundHandle + 1, characteristicReadError); } + } - synchronized (readWriteQueue) { - ioJobPending = false; - } + if (isServiceDiscoveryRun) { - if (foundHandle == -1) { - Log.w(TAG, "Out-of-detail-discovery: char update failed. " + - "Cannot find handle for characteristic"); - } else { - if (status == BluetoothGatt.GATT_SUCCESS) { - 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); - } - } + // 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); + } - performNextIO(); - return; + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; } - GattEntry entry = entries.get(runningHandle); - entry.valueKnown = true; - entries.set(runningHandle, entry); - - // 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(), - runningHandle + 1, characteristic.getUuid().toString(), - characteristic.getProperties(), characteristic.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); + performNextIO(); } public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, @@ -255,6 +290,15 @@ public class QtBluetoothLE { return; } + boolean requestTimedOut = !handleForTimeout.compareAndSet( + modifiedReadWriteHandle(handle, IoJobType.Write), -1); + 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 ioJobPending -> the timeout has done that already + return; + } + int errorCode; //This must be in sync with QLowEnergyService::ServiceError switch (status) { @@ -287,81 +331,83 @@ public class QtBluetoothLE { android.bluetooth.BluetoothGattDescriptor descriptor, int status) { - //runningHandle is only used during serviceDetailsDiscovery - //If it is -1 we got an update outside of the details discovery process - final boolean isServiceDiscoveryRun = (runningHandle != -1); - - if (status != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "onDescriptorRead error: " + status); - - // read errors during serviceDiscovery are ignored - if (isServiceDiscoveryRun) - return; - } - + int foundHandle = -1; synchronized (this) { - if (uuidToEntry.isEmpty()) // ignore data if internal setup is not ready; + 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) { + ioJobPending = false; + } + performNextIO(); return; + } } + boolean requestTimedOut = !handleForTimeout.compareAndSet( + modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1); + 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 ioJobPending -> the timeout has done that already + return; + } - if (!isServiceDiscoveryRun) { + GattEntry entry = entries.get(foundHandle); + final boolean isServiceDiscoveryRun = !entry.valueKnown; + entry.valueKnown = true; - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForDescriptor(descriptor); + 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) { + //ignore + Log.w(TAG, "onDescriptorcRead during discovery error: " + status); + } else { + // This must be in sync with QLowEnergyService::DescriptorReadError + final int descriptorReadError = 6; + leServiceError(qtObject, foundHandle + 1, descriptorReadError); } - synchronized (readWriteQueue) { - ioJobPending = false; + } + + 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); } - if (foundHandle == -1) { - Log.w(TAG, "Out-of-detail-discovery: char update failed. " + - "Cannot find handle for descriptor."); - } else { - if (status == BluetoothGatt.GATT_SUCCESS) { - 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); + /* 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) { + final int value = descriptor.getValue()[0]; + // notification or indication bit set? + if ((value & 0x03) > 0) { + Log.d(TAG, "Found descriptor with automatic notifications."); + mBluetoothGatt.setCharacteristicNotification( + descriptor.getCharacteristic(), true); } } - - performNextIO(); - return; } - - GattEntry entry = entries.get(runningHandle); - entry.valueKnown = true; - entries.set(runningHandle, entry); - //TODO avoid sending service and characteristic uuid -> handles should be sufficient - leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), - descriptor.getCharacteristic().getUuid().toString(), runningHandle+1, - descriptor.getUuid().toString(), descriptor.getValue()); - - /* 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) { - final int value = descriptor.getValue()[0]; - // notification or indication bit set? - if ((value & 0x03) > 0) { - Log.d(TAG, "Found descriptor with automatic notifications."); - mBluetoothGatt.setCharacteristicNotification( - descriptor.getCharacteristic(), true); - } + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; } - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); + performNextIO(); } public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, @@ -373,6 +419,16 @@ public class QtBluetoothLE { int handle = handleForDescriptor(descriptor); + boolean requestTimedOut = !handleForTimeout.compareAndSet( + modifiedReadWriteHandle(handle, IoJobType.Write), -1); + 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 ioJobPending -> the timeout has done that already + return; + } + int errorCode; //This must be in sync with QLowEnergyService::ServiceError switch (status) { @@ -440,7 +496,15 @@ public class QtBluetoothLE { public BluetoothGattService service = null; public BluetoothGattCharacteristic characteristic = null; public BluetoothGattDescriptor descriptor = null; - public int endHandle; + /* + * endHandle defined for GattEntryType.Service and GattEntryType.CharacteristicValue + * If the type is service this is the value of the last Gatt entry belonging to the very + * same service. If the type is a char value it is the entries index inside + * the "entries" list. + */ + public int endHandle = -1; + // pointer back to the handle that describes the service that this GATT entry belongs to + public int associatedServiceHandle; } private enum IoJobType @@ -456,8 +520,12 @@ public class QtBluetoothLE { public IoJobType jobType; } + // service uuid -> service handle mapping (there can be more than one service with same uuid) private final Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100); + // 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>(); @@ -557,10 +625,12 @@ public class QtBluetoothLE { GattEntry serviceEntry = new GattEntry(); serviceEntry.type = GattEntryType.Service; serviceEntry.service = service; - entries.add(entry); + entries.add(serviceEntry); // remember handle for the service for later update int serviceHandle = entries.size() - 1; + //point to itself -> mostly done for consistence reasons with other entries + serviceEntry.associatedServiceHandle = serviceHandle; //some devices may have more than one service with the same uuid List<Integer> old = uuidToEntry.get(service.getUuid()); @@ -575,11 +645,15 @@ public class QtBluetoothLE { entry = new GattEntry(); entry.type = GattEntryType.Characteristic; entry.characteristic = characteristic; + entry.associatedServiceHandle = serviceHandle; + //entry.endHandle = .. undefined entries.add(entry); // this emulates GATT value attributes entry = new GattEntry(); entry.type = GattEntryType.CharacteristicValue; + entry.associatedServiceHandle = serviceHandle; + entry.endHandle = entries.size(); // special case -> current index in entries list entries.add(entry); // add all descriptors @@ -588,30 +662,31 @@ public class QtBluetoothLE { entry = new GattEntry(); entry.type = GattEntryType.Descriptor; entry.descriptor = desc; + entry.associatedServiceHandle = serviceHandle; + //entry.endHandle = .. undefined entries.add(entry); } } // update endHandle of current service serviceEntry.endHandle = entries.size() - 1; - entries.set(serviceHandle, serviceEntry); } entries.trimToSize(); } - private int currentServiceInDiscovery = -1; - private int runningHandle = -1; - private void resetData() { synchronized (this) { - runningHandle = -1; - currentServiceInDiscovery = -1; uuidToEntry.clear(); entries.clear(); servicesToBeDiscovered.clear(); } + + // kill all timeout handlers + timeoutHandler.removeCallbacksAndMessages(null); + handleForTimeout.set(-1); + synchronized (readWriteQueue) { readWriteQueue.clear(); } @@ -654,28 +729,15 @@ public class QtBluetoothLE { return false; } - // current service already under investigation - if (currentServiceInDiscovery == serviceHandle) - return true; - - if (currentServiceInDiscovery != -1) { - // we are currently discovering another service - // we queue the new one up until we finish the previous one - if (!entry.valueKnown) { - servicesToBeDiscovered.add(serviceHandle); - Log.w(TAG, "Service discovery already running on another service, " + - "queueing request for " + serviceUuid); - } else { - Log.w(TAG, "Service already known"); - } + // current service already discovered or under investigation + if (entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) { + Log.w(TAG, "Service already known or to be discovered"); return true; } - if (!entry.valueKnown) { - performServiceDetailDiscoveryForHandle(serviceHandle, true); - } else { - Log.w(TAG, "Service already discovered"); - } + servicesToBeDiscovered.add(serviceHandle); + scheduleServiceDetailDiscovery(serviceHandle); + performNextIO(); } catch (Exception ex) { ex.printStackTrace(); @@ -716,98 +778,70 @@ public class QtBluetoothLE { return builder.toString(); } - private void finishCurrentServiceDiscovery() + //TODO function not yet used + private void finishCurrentServiceDiscovery(int handleDiscoveredService) { - int currentEntry = currentServiceInDiscovery; - GattEntry discoveredService = entries.get(currentServiceInDiscovery); + Log.w(TAG, "Finished current discovery for service handle " + handleDiscoveredService); + GattEntry discoveredService = entries.get(handleDiscoveredService); discoveredService.valueKnown = true; - entries.set(currentServiceInDiscovery, discoveredService); - - runningHandle = -1; - currentServiceInDiscovery = -1; - - leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(), - currentEntry + 1, discoveredService.endHandle + 1); - - if (!servicesToBeDiscovered.isEmpty()) { + synchronized (this) { try { - int nextService = servicesToBeDiscovered.remove(); - performServiceDetailDiscoveryForHandle(nextService, true); - } catch (IndexOutOfBoundsException ex) { + 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); } - private synchronized void performServiceDetailDiscoveryForHandle(int nextHandle, boolean searchStarted) + /* + Internal Helper function for discoverServiceDetails() + + 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) { - try { - if (searchStarted) { - currentServiceInDiscovery = nextHandle; - runningHandle = ++nextHandle; - } else { - runningHandle = nextHandle; - } + GattEntry serviceEntry = entries.get(serviceHandle); + final int endHandle = serviceEntry.endHandle; - GattEntry entry; - try { - entry = entries.get(nextHandle); - } catch (IndexOutOfBoundsException ex) { - //ex.printStackTrace(); - Log.w(TAG, "Last entry of last service read"); - finishCurrentServiceDiscovery(); - 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 - boolean result; - switch (entry.type) { - case Characteristic: - result = mBluetoothGatt.readCharacteristic(entry.characteristic); - try { - if (!result) { - // add characteristic now since we won't get a read update later one - // this is possible when the characteristic is not readable - Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() + - " for service " + entry.characteristic.getService().getUuid()); - leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(), - nextHandle + 1, entry.characteristic.getUuid().toString(), - entry.characteristic.getProperties(), entry.characteristic.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - } - } catch (Exception ex) - { - ex.printStackTrace(); - } - break; - case CharacteristicValue: - // ignore -> nothing to do for this artificial type - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - break; - case Descriptor: - result = mBluetoothGatt.readDescriptor(entry.descriptor); - if (!result) { - // atm all descriptor types are readable - Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() + - " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() + - "/" + entry.descriptor.getCharacteristic().getUuid()); - leDescriptorRead(qtObject, - entry.descriptor.getCharacteristic().getService().getUuid().toString(), - entry.descriptor.getCharacteristic().getUuid().toString(), - nextHandle+1, entry.descriptor.getUuid().toString(), - entry.descriptor.getValue()); - performServiceDetailDiscoveryForHandle(runningHandle + 1, false); - } - break; - case Service: - finishCurrentServiceDiscovery(); - break; - default: - Log.w(TAG, "Invalid GATT attribute type"); - break; - } - } catch(Exception ex) { - ex.printStackTrace(); + // 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; + + final boolean result = readWriteQueue.add(newJob); + if (!result) + Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid() + + " on item " + entry.type); + } } } @@ -964,6 +998,30 @@ public class QtBluetoothLE { return true; } + // Called by TimeoutRunnable if the current I/O job timeoutd 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) + { + //unlock the queue for next item + synchronized (readWriteQueue) { + ioJobPending = false; + } + + performNextIO(); + + 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); + } + /* The queuing is required because two writeCharacteristic/writeDescriptor calls cannot execute at the same time. The second write must happen after the @@ -976,50 +1034,111 @@ public class QtBluetoothLE { boolean skip = false; final ReadWriteJob nextJob; + int handle = -1; + synchronized (readWriteQueue) { if (readWriteQueue.isEmpty() || ioJobPending) return; nextJob = readWriteQueue.remove(); + 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; + } + + // 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)); - Log.w(TAG, "Performing queued job " + nextJob.jobType); if (nextJob.jobType == IoJobType.Read) skip = executeReadJob(nextJob); else skip = executeWriteJob(nextJob); - if (!skip) + if (skip) { + handleForTimeout.set(-1); // not a pending call -> release atomic + } else { ioJobPending = true; + timeoutHandler.postDelayed(new TimeoutRunnable( + modifiedReadWriteHandle(handle, nextJob.jobType)), RUNNABLE_TIMEOUT); + } + + 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); } - if (skip) { - Log.w(TAG, "Skipping: " + nextJob.entry.type); + GattEntry entry = nextJob.entry; + if (skip) { /* BluetoothGatt.[read|write][Characteristic|Descriptor]() immediately return in cases where meta data doesn't match the intended action (e.g. trying to write to read-only char). When this happens - we have to report an error back to Qt. This is not required during + we have to report an error back to Qt. The error report is not required during the initial service discovery though. */ - final boolean isServiceDiscoveryRun = (runningHandle != -1); - if (!isServiceDiscoveryRun) { - int handle = -1; - if (nextJob.entry.type == GattEntryType.Characteristic) - handle = handleForCharacteristic(nextJob.entry.characteristic); - else - handle = handleForDescriptor(nextJob.entry.descriptor); + if (handle != -1) { + // during service discovery we do not report error but emit characteristicRead() + // any other time a failure emits serviceError() signal + + final boolean isServiceDiscovery = !entry.valueKnown; - if (handle != -1) { + if (isServiceDiscovery) { + entry.valueKnown = true; + switch (entry.type) { + case Characteristic: + Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() + + " for service " + entry.characteristic.getService().getUuid()); + leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(), + handle + 1, entry.characteristic.getUuid().toString(), + entry.characteristic.getProperties(), entry.characteristic.getValue()); + break; + case Descriptor: + // atm all descriptor types are readable + Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() + + " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() + + "/" + entry.descriptor.getCharacteristic().getUuid()); + leDescriptorRead(qtObject, + entry.descriptor.getCharacteristic().getService().getUuid().toString(), + entry.descriptor.getCharacteristic().getUuid().toString(), + handle + 1, entry.descriptor.getUuid().toString(), + entry.descriptor.getValue()); + break; + case CharacteristicValue: + // for more details see scheduleServiceDetailDiscovery(int) + // ignore and continue unless last entry + GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); + if (serviceEntry.endHandle == handle) + finishCurrentServiceDiscovery(entry.associatedServiceHandle); + break; + case Service: + Log.w(TAG, "Scheduling of Service Gatt entry for service discovery should never happen."); + break; + } + } else { int errorCode = 0; // The error codes below must be in sync with QLowEnergyService::ServiceError if (nextJob.jobType == IoJobType.Read) { - errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ? - 5 : 6; // CharacteristicReadError : DescriptorReadError + errorCode = (entry.type == GattEntryType.Characteristic) ? + 5 : 6; // CharacteristicReadError : DescriptorReadError } else { - errorCode = (nextJob.entry.type == GattEntryType.Characteristic) ? - 2 : 3; // CharacteristicWriteError : DescriptorWriteError + errorCode = (entry.type == GattEntryType.Characteristic) ? + 2 : 3; // CharacteristicWriteError : DescriptorWriteError } leServiceError(qtObject, handle + 1, errorCode); @@ -1109,12 +1228,52 @@ public class QtBluetoothLE { return true; // skip break; case Service: - case CharacteristicValue: return true; + case CharacteristicValue: + return true; //skip } return false; } + /* + * Modifies and returns the given \a handle such that the job + * \a type is encoded into the returned handle. Hereby we take advantage of the fact that + * a Bluetooth Low Energy handle is only 16 bit. The handle will be the bottom two bytes + * and the job type will be in the top 2 bytes. + * + * top 2 bytes + * - 0x01 -> Read Job + * - 0x02 -> Write Job + * + * This is done in connection with handleForTimeout and assists in the process of + * detecting accidental interruption by the timeout handler. + * If two requests for the same handle are scheduled behind each other there is the + * theoretical chance that the first request comes back normally while the second request + * is interrupted by the timeout handler. This risk still exists but this function ensures that + * at least back to back requests of differing types cannot affect each other via the timeout + * handler. + */ + private int modifiedReadWriteHandle(int handle, IoJobType type) + { + int modifiedHandle = handle; + // ensure we have 16bit handle only + if (handle > 0xFFFF) + Log.w(TAG, "Invalid handle"); + + modifiedHandle = (modifiedHandle & 0xFFFF); + + switch (type) { + case Write: + modifiedHandle = (modifiedHandle | 0x00010000); + break; + case Read: + modifiedHandle = (modifiedHandle | 0x00020000); + break; + } + + return modifiedHandle; + } + public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState); public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList); public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid, diff --git a/src/bluetooth/qbluetoothlocaldevice_android.cpp b/src/bluetooth/qbluetoothlocaldevice_android.cpp index 72162fd4..f36e184c 100644 --- a/src/bluetooth/qbluetoothlocaldevice_android.cpp +++ b/src/bluetooth/qbluetoothlocaldevice_android.cpp @@ -87,18 +87,36 @@ QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter() return obj; } -void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address) +static QAndroidJniObject getDefaultAdapter() { - QAndroidJniEnvironment env; - QAndroidJniObject adapter = QAndroidJniObject::callStaticObjectMethod( "android/bluetooth/BluetoothAdapter", "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); if (!adapter.isValid()) { + QAndroidJniEnvironment env; if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } + + // workaround stupid bt implementations where first call of BluetoothAdapter.getDefaultAdapter() always fails + adapter = QAndroidJniObject::callStaticObjectMethod( + "android/bluetooth/BluetoothAdapter", "getDefaultAdapter", + "()Landroid/bluetooth/BluetoothAdapter;"); + if (!adapter.isValid()) { + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + } + return adapter; +} + +void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address) +{ + QAndroidJniObject adapter = getDefaultAdapter(); + if (!adapter.isValid()) { qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; return; } @@ -304,33 +322,7 @@ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() // Android only supports max of one device (so far) QList<QBluetoothHostInfo> localDevices; - QAndroidJniEnvironment env; - jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter"); - if (btAdapterClass == NULL) { - qCWarning(QT_BT_ANDROID) - << "Native registration unable to find class android/bluetooth/BluetoothAdapter"; - return localDevices; - } - - jmethodID getDefaultAdapterID - = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter", - "()Landroid/bluetooth/BluetoothAdapter;"); - if (getDefaultAdapterID == NULL) { - qCWarning(QT_BT_ANDROID) - << "Native registration unable to get method ID: " \ - "getDefaultAdapter of android/bluetooth/BluetoothAdapter"; - env->DeleteLocalRef(btAdapterClass); - return localDevices; - } - - jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID); - if (btAdapterObject == NULL) { - qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; - env->DeleteLocalRef(btAdapterClass); - return localDevices; - } - - QAndroidJniObject o(btAdapterObject); + QAndroidJniObject o = getDefaultAdapter(); if (o.isValid()) { QBluetoothHostInfo info; info.setName(o.callObjectMethod("getName", "()Ljava/lang/String;").toString()); @@ -338,10 +330,6 @@ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() "()Ljava/lang/String;").toString())); localDevices.append(info); } - - env->DeleteLocalRef(btAdapterObject); - env->DeleteLocalRef(btAdapterClass); - return localDevices; } diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp index ab8cd876..56d4f77b 100644 --- a/src/bluetooth/qbluetoothsocket_android.cpp +++ b/src/bluetooth/qbluetoothsocket_android.cpp @@ -252,15 +252,15 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); - } - - if (socketChannel - == remoteDevice.getStaticField<jint>("android/bluetooth/BluetoothDevice", "ERROR") - || socketChannel == -1) { - qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel."; } else { - qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel" << socketChannel; - channel = socketChannel; + if (socketChannel + == remoteDevice.getStaticField<jint>("android/bluetooth/BluetoothDevice", "ERROR") + || socketChannel == -1) { + qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel."; + } else { + qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel" << socketChannel; + channel = socketChannel; + } } } diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index f6bf0ae3..dcbdcdb9 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -457,6 +457,11 @@ Q_DECLARE_TYPEINFO(QLowEnergyControllerPrivate::Attribute, Q_MOVABLE_TYPE); QT_END_NAMESPACE +#ifdef QT_WINRT_BLUETOOTH +Q_DECLARE_METATYPE(QLowEnergyCharacteristic) +Q_DECLARE_METATYPE(QLowEnergyDescriptor) +#endif // QT_WINRT_BLUETOOTH + #endif // QT_OSX_BLUETOOTH || QT_IOS_BLUETOOTH #endif // QLOWENERGYCONTROLLERPRIVATE_P_H diff --git a/src/bluetooth/qlowenergycontroller_winrt.cpp b/src/bluetooth/qlowenergycontroller_winrt.cpp index a39078df..d6fc4952 100644 --- a/src/bluetooth/qlowenergycontroller_winrt.cpp +++ b/src/bluetooth/qlowenergycontroller_winrt.cpp @@ -277,6 +277,9 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() error(QLowEnergyController::NoError) { qCDebug(QT_BT_WINRT) << __FUNCTION__; + + qRegisterMetaType<QLowEnergyCharacteristic>(); + qRegisterMetaType<QLowEnergyDescriptor>(); } QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp index e9722f81..6e33c565 100644 --- a/src/bluetooth/qlowenergyservice.cpp +++ b/src/bluetooth/qlowenergyservice.cpp @@ -294,6 +294,8 @@ QT_BEGIN_NAMESPACE the read operation is not successful, the \l error() signal is emitted using the \l CharacteristicReadError flag. + \note This signal is only emitted for Central Role related use cases. + \sa readCharacteristic() \since 5.5 */ @@ -306,18 +308,14 @@ QT_BEGIN_NAMESPACE by calling \l writeCharacteristic(). If the write operation is not successful, the \l error() signal is emitted using the \l CharacteristicWriteError flag. - Since this signal is an indication of a successful write operation \a newValue - generally matches the value that was passed to the associated - \l writeCharacteristic() call. However, it may happen that the two values differ - from each other. This can occur in cases when the written value is - used by the remote device to trigger an operation and it returns some other value via - the written and/or change notification. Such cases are very specific to the - target device. In any case, the reception of the written signal can still be considered - as a sign that the target device received the to-be-written value. + The reception of the written signal can be considered as a sign that the target device + received the to-be-written value and reports back the status of write request. \note If \l writeCharacteristic() is called using the \l WriteWithoutResponse mode, this signal and the \l error() are never emitted. + \note This signal is only emitted for Central Role related use cases. + \sa writeCharacteristic() */ @@ -326,7 +324,7 @@ QT_BEGIN_NAMESPACE If the associated controller object is in the \l {QLowEnergyController::CentralRole}{central} role, this signal is emitted when the value of \a characteristic is changed by an event on the - peripheral. In that case, the signal emission implies that change notifications must + peripheral/device side. In that case, the signal emission implies that change notifications must have been activated via the characteristic's \l {QBluetoothUuid::ClientCharacteristicConfiguration}{ClientCharacteristicConfiguration} descriptor prior to the change event on the peripheral. More details on how this might be @@ -348,6 +346,8 @@ QT_BEGIN_NAMESPACE the read operation is not successful, the \l error() signal is emitted using the \l DescriptorReadError flag. + \note This signal is only emitted for Central Role related use cases. + \sa readDescriptor() \since 5.5 */ @@ -648,7 +648,8 @@ void QLowEnergyService::readCharacteristic( The call results in a write request or command to a remote peripheral. If the operation is successful, the \l characteristicWritten() signal is emitted; otherwise the \l CharacteristicWriteError - is set. + is set. Calling this function does not trigger the a \l characteristicChanged() + signal unless the peripheral itself changes the value again after the current write request. The \a mode parameter determines whether the remote device should send a write confirmation. The to-be-written \a characteristic must support the relevant diff --git a/src/imports/bluetooth/plugin.cpp b/src/imports/bluetooth/plugin.cpp index d6c47e35..f77a4d95 100644 --- a/src/imports/bluetooth/plugin.cpp +++ b/src/imports/bluetooth/plugin.cpp @@ -81,9 +81,9 @@ public: qmlRegisterType<QDeclarativeBluetoothService >(uri, major, minor, "BluetoothService"); qmlRegisterType<QDeclarativeBluetoothSocket >(uri, major, minor, "BluetoothSocket"); - // Register the 5.7 types - // introduces 5.7 version, other existing 5.2 exports become automatically available under 5.2-5.6 - minor = 7; + // Register the 5.8 types + // introduces 5.8 version, other existing 5.2 exports become automatically available under 5.2-5.7 + minor = 8; qmlRegisterType<QDeclarativeBluetoothDiscoveryModel >(uri, major, minor, "BluetoothDiscoveryModel"); } }; diff --git a/src/imports/bluetooth/plugins.qmltypes b/src/imports/bluetooth/plugins.qmltypes index 9c47126d..0ffe3223 100644 --- a/src/imports/bluetooth/plugins.qmltypes +++ b/src/imports/bluetooth/plugins.qmltypes @@ -4,17 +4,17 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtBluetooth 5.7' +// 'qmlplugindump -nonrelocatable QtBluetooth 5.8' Module { - dependencies: ["QtQuick 2.0"] + dependencies: ["QtQuick 2.8"] Component { name: "QDeclarativeBluetoothDiscoveryModel" prototype: "QAbstractListModel" exports: [ "QtBluetooth/BluetoothDiscoveryModel 5.0", "QtBluetooth/BluetoothDiscoveryModel 5.2", - "QtBluetooth/BluetoothDiscoveryModel 5.7" + "QtBluetooth/BluetoothDiscoveryModel 5.8" ] exportMetaObjectRevisions: [0, 0, 0] Enum { diff --git a/src/imports/nfc/plugin.cpp b/src/imports/nfc/plugin.cpp index b71f1f24..d003f531 100644 --- a/src/imports/nfc/plugin.cpp +++ b/src/imports/nfc/plugin.cpp @@ -98,8 +98,8 @@ public: minor = 5; qmlRegisterType<QDeclarativeNearField, 1>(uri, major, minor, "NearField"); - // Register the 5.6 - 5.7 types - minor = 7; + // Register the 5.6 - 5.8 types + minor = 8; qmlRegisterType<QDeclarativeNearField, 1>(uri, major, minor, "NearField"); } diff --git a/src/imports/nfc/plugins.qmltypes b/src/imports/nfc/plugins.qmltypes index 7bba6d95..c9fae196 100644 --- a/src/imports/nfc/plugins.qmltypes +++ b/src/imports/nfc/plugins.qmltypes @@ -4,10 +4,10 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtNfc 5.7' +// 'qmlplugindump -nonrelocatable QtNfc 5.8' Module { - dependencies: ["QtQuick 2.0"] + dependencies: ["QtQuick 2.8"] Component { name: "QDeclarativeNdefFilter" prototype: "QObject" @@ -58,7 +58,7 @@ Module { "QtNfc/NearField 5.2", "QtNfc/NearField 5.4", "QtNfc/NearField 5.5", - "QtNfc/NearField 5.7" + "QtNfc/NearField 5.8" ] exportMetaObjectRevisions: [0, 0, 0, 1, 1] Property { name: "messageRecords"; type: "QQmlNdefRecord"; isList: true; isReadonly: true } diff --git a/src/src.pro b/src/src.pro index 8c54e4fb..38be9a4f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -24,5 +24,7 @@ qtHaveModule(quick) { include($$OUT_PWD/bluetooth/qtbluetooth-config.pri) QT_FOR_CONFIG += bluetooth-private qtConfig(bluez):qtHaveModule(dbus) { - SUBDIRS += tools/sdpscanner + sdpscanner.subdir = tools/sdpscanner + sdpscanner.depends = bluetooth + SUBDIRS += sdpscanner } diff --git a/src/tools/sdpscanner/qt_attribution.json b/src/tools/sdpscanner/qt_attribution.json index ee01349e..9f2ada92 100644 --- a/src/tools/sdpscanner/qt_attribution.json +++ b/src/tools/sdpscanner/qt_attribution.json @@ -2,7 +2,7 @@ "Id": "bluez", "Name": "BlueZ", "QDocModule": "qtbluetooth", - "QtUsage": "On Linux, Qt Bluetooth uses a separate executable, \c sdpscanner, to integrate with the official Linux bluetooth protocol stack BlueZ. The Qt Bluetooth library does not link against BlueZ directly.", + "QtUsage": "On Linux, Qt Bluetooth uses a separate executable, sdpscanner, to integrate with the official Linux bluetooth protocol stack BlueZ. The Qt Bluetooth library does not link against BlueZ directly.", "Description": "BlueZ", "Homepage": "http://www.bluez.org/", |