summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@qt.io>2017-01-13 15:15:36 +0100
committerAlex Blasche <alexander.blasche@qt.io>2017-01-13 15:15:36 +0100
commit117db19dbc0c16e67812145d47d2d3e29a7f355d (patch)
tree61f0a13064790585c4a8f15c99050cccbef92ebc
parent5cd2df95d14d76f4f96b119e6ca6faff0a212fb2 (diff)
parent096a4694427d34dd3a0ce5a7aeb8d2011fb96c8b (diff)
Merge remote-tracking branch 'gerrit/5.8' into dev
-rw-r--r--dist/changes-5.8.076
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java625
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp5
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp5
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_android.cpp56
-rw-r--r--src/bluetooth/qbluetoothsocket_android.cpp16
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp4
-rw-r--r--src/bluetooth/qlowenergycontroller_bluez.cpp126
-rw-r--r--src/bluetooth/qlowenergycontroller_osx.mm10
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h27
-rw-r--r--src/bluetooth/qlowenergycontroller_winrt.cpp3
-rw-r--r--src/bluetooth/qlowenergyservice.cpp21
-rw-r--r--src/imports/bluetooth/plugin.cpp6
-rw-r--r--src/imports/bluetooth/plugins.qmltypes6
-rw-r--r--src/imports/nfc/plugin.cpp4
-rw-r--r--src/imports/nfc/plugins.qmltypes6
-rw-r--r--src/src.pro4
-rw-r--r--src/tools/sdpscanner/qt_attribution.json2
18 files changed, 699 insertions, 303 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..3348e4de 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"})
@@ -152,6 +184,8 @@ public class QtBluetoothLE {
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
errorCode = 0; break; //QLowEnergyController::NoError
+ case 8: // link loss
+ errorCode = 5; break; //QLowEnergyController::ConnectionError
default:
Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status);
errorCode = status; break; //TODO deal with all errors
@@ -182,64 +216,72 @@ 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) {
+ 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);
}
+ }
- 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 +297,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 +338,84 @@ 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) {
+ 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();
- 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 +427,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 +504,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 +528,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 +633,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 +653,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 +670,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 +737,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 +786,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 +1006,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 +1042,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 +1236,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/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index c807df7f..9c9c0409 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -511,7 +511,8 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
if (scanRecord != nullptr) {
// Parse scan record
jboolean isCopy;
- const char *scanRecordBuffer = reinterpret_cast<const char *>(env->GetByteArrayElements(scanRecord, &isCopy));
+ jbyte *elems = env->GetByteArrayElements(scanRecord, &isCopy);
+ const char *scanRecordBuffer = reinterpret_cast<const char *>(elems);
const int scanRecordLength = env->GetArrayLength(scanRecord);
QList<QBluetoothUuid> serviceUuids;
@@ -560,6 +561,8 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
}
info.setServiceUuids(serviceUuids, QBluetoothDeviceInfo::DataIncomplete);
+
+ env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT);
}
if (QtAndroidPrivate::androidSdkVersion() >= 18) {
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
index 2d6e64be..e76ddff7 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -45,6 +45,7 @@
#include <QtCore/private/qjnihelpers_p.h>
#include "android/devicediscoverybroadcastreceiver_p.h"
#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtAndroid>
QT_BEGIN_NAMESPACE
@@ -147,7 +148,9 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent
// check Android v23+ permissions
// -> BTLE search requires android.permission.ACCESS_COARSE_LOCATION
- if (requestedMethods && QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) {
+ if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod
+ && QtAndroid::androidSdkVersion() >= 23)
+ {
QString permission(QLatin1String("android.permission.ACCESS_COARSE_LOCATION"));
// do we have required permission already, if so nothing to do
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.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 4df92a39..03278276 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -737,6 +737,10 @@ void QLowEnergyController::connectToDevice()
This function does nothing if the controller is in the \l UnconnectedState.
+ If the controller is in the peripheral role, it stops advertising too.
+ The application must restart the advertising mode by calling
+ \l startAdvertising().
+
\sa connectToDevice()
*/
void QLowEnergyController::disconnectFromDevice()
diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp
index e85fcb34..f8d2ba2a 100644
--- a/src/bluetooth/qlowenergycontroller_bluez.cpp
+++ b/src/bluetooth/qlowenergycontroller_bluez.cpp
@@ -48,6 +48,7 @@
#include <QtCore/QFileInfo>
#include <QtCore/QLoggingCategory>
#include <QtCore/QSettings>
+#include <QtCore/QTimer>
#include <QtBluetooth/QBluetoothLocalDevice>
#include <QtBluetooth/QBluetoothSocket>
#include <QtBluetooth/QLowEnergyCharacteristicData>
@@ -132,6 +133,13 @@
#define ATT_ERROR_UNSUPPRTED_GROUP_TYPE 0x10
#define ATT_ERROR_INSUF_RESOURCES 0x11
#define ATT_ERROR_APPLICATION_START 0x80
+//------------------------------------------
+// The error codes in this block are
+// implementation specific errors
+
+#define ATT_ERROR_REQUEST_STALLED 0x81
+
+//------------------------------------------
#define ATT_ERROR_APPLICATION_END 0x9f
#define APPEND_VALUE true
@@ -211,7 +219,7 @@ static void dumpErrorInformation(const QByteArray &response)
errorString = QStringLiteral("insufficient resources to complete request"); break;
default:
if (errorCode >= ATT_ERROR_APPLICATION_START && errorCode <= ATT_ERROR_APPLICATION_END)
- errorString = QStringLiteral("application error");
+ errorString = QStringLiteral("application error: %1").arg(errorCode);
else
errorString = QStringLiteral("unknown error code");
break;
@@ -304,6 +312,100 @@ void QLowEnergyControllerPrivate::init()
signingData.insert(remoteDevice.toUInt64(), SigningData(csrk));
}
);
+
+ if (role == QLowEnergyController::CentralRole) {
+ if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) {
+ bool ok = false;
+ int value = qEnvironmentVariableIntValue("BLUETOOTH_GATT_TIMEOUT", &ok);
+ if (ok)
+ gattRequestTimeout = value;
+ }
+
+ // permit disabling of timeout behavior via environment variable
+ if (gattRequestTimeout > 0) {
+ qCWarning(QT_BT_BLUEZ) << "Enabling GATT request timeout behavior" << gattRequestTimeout;
+ requestTimer = new QTimer(this);
+ requestTimer->setSingleShot(true);
+ requestTimer->setInterval(gattRequestTimeout);
+ connect(requestTimer, &QTimer::timeout,
+ this, &QLowEnergyControllerPrivate::handleGattRequestTimeout);
+ }
+ }
+}
+
+void QLowEnergyControllerPrivate::handleGattRequestTimeout()
+{
+ // antyhing open that might require cancellation or a warning?
+ if (encryptionChangePending) {
+ // We cannot really recover for now but the warning is essential for debugging
+ qCWarning(QT_BT_BLUEZ) << "****** Encryption change event blocking further GATT requests";
+ return;
+ }
+
+ if (!openRequests.isEmpty() && requestPending) {
+ requestPending = false; // reset pending flag
+ const Request currentRequest = openRequests.dequeue();
+
+ qCWarning(QT_BT_BLUEZ).nospace() << "****** Request type 0x" << hex << currentRequest.command
+ << " to server/peripheral timed out";
+ qCWarning(QT_BT_BLUEZ) << "****** Looks like the characteristic or descriptor does NOT act in"
+ << "accordance to Bluetooth 4.x spec.";
+ qCWarning(QT_BT_BLUEZ) << "****** Please check server implementation."
+ << "Continuing under reservation.";
+
+ quint8 command = currentRequest.command;
+ const auto createRequestErrorMessage = [](quint8 opcodeWithError,
+ QLowEnergyHandle handle) {
+ QByteArray errorPackage(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
+ errorPackage[0] = ATT_OP_ERROR_RESPONSE;
+ errorPackage[1] = opcodeWithError; // e.g. ATT_OP_READ_REQUEST
+ putBtData(handle, errorPackage.data() + 2); //
+ errorPackage[4] = ATT_ERROR_REQUEST_STALLED;
+
+ return errorPackage;
+ };
+
+ switch (command) {
+ case ATT_OP_EXCHANGE_MTU_REQUEST: // MTU change request
+ // never received reply to MTU request
+ // it is safe to skip and go to next request
+ sendNextPendingRequest();
+ break;
+ case ATT_OP_READ_BY_GROUP_REQUEST: // primary or secondary service discovery
+ case ATT_OP_READ_BY_TYPE_REQUEST: // characteristic or included service discovery
+ // jump back into usual response handling with custom error code
+ // 2nd param "0" as required by spec
+ processReply(currentRequest, createRequestErrorMessage(command, 0));
+ break;
+ case ATT_OP_READ_REQUEST: // read descriptor or characteristic value
+ case ATT_OP_READ_BLOB_REQUEST: // read long descriptor or characteristic
+ case ATT_OP_WRITE_REQUEST: // write descriptor or characteristic
+ {
+ uint handleData = currentRequest.reference.toUInt();
+ const QLowEnergyHandle charHandle = (handleData & 0xffff);
+ const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
+ processReply(currentRequest, createRequestErrorMessage(command,
+ descriptorHandle ? descriptorHandle : charHandle));
+ }
+ break;
+ case ATT_OP_FIND_INFORMATION_REQUEST: // get descriptor information
+ processReply(currentRequest, createRequestErrorMessage(
+ command, currentRequest.reference2.toUInt()));
+ break;
+ case ATT_OP_PREPARE_WRITE_REQUEST: // prepare to write long desc or char
+ case ATT_OP_EXECUTE_WRITE_REQUEST: // execute long write of desc or char
+ {
+ uint handleData = currentRequest.reference.toUInt();
+ const QLowEnergyHandle attrHandle = (handleData & 0xffff);
+ processReply(currentRequest,
+ createRequestErrorMessage(command, attrHandle));
+ }
+ break;
+ default:
+ // not a command used by central role implementation
+ return;
+ }
+ }
}
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
@@ -537,6 +639,19 @@ void QLowEnergyControllerPrivate::resetController()
receivedMtuExchangeRequest = false;
securityLevelValue = -1;
connectionHandle = 0;
+
+ // public API behavior requires stop of advertisement
+ if (role == QLowEnergyController::PeripheralRole && advertiser)
+ advertiser->stopAdvertising();
+}
+
+void QLowEnergyControllerPrivate::restartRequestTimer()
+{
+ if (!requestTimer)
+ return;
+
+ if (gattRequestTimeout > 0)
+ requestTimer->start(gattRequestTimeout);
}
void QLowEnergyControllerPrivate::l2cpReadyRead()
@@ -564,7 +679,8 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
processUnsolicitedReply(incomingPacket);
return;
}
-
+ //--------------------------------------------------
+ // Peripheral side packet handling
case ATT_OP_EXCHANGE_MTU_REQUEST:
handleExchangeMtuRequest(incomingPacket);
return;
@@ -608,6 +724,7 @@ void QLowEnergyControllerPrivate::l2cpReadyRead()
qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation";
}
return;
+ //--------------------------------------------------
default:
//only solicited replies finish pending requests
requestPending = false;
@@ -713,6 +830,7 @@ void QLowEnergyControllerPrivate::sendNextPendingRequest()
// << request.payload.toHex();
requestPending = true;
+ restartRequestTimer();
sendPacket(request.payload);
}
@@ -1920,8 +2038,10 @@ bool QLowEnergyControllerPrivate::increaseEncryptLevelfRequired(quint8 errorCode
return false;
if (securityLevelValue != BT_SECURITY_HIGH) {
qCDebug(QT_BT_BLUEZ) << "Requesting encrypted link";
- if (setSecurityLevel(BT_SECURITY_HIGH))
+ if (setSecurityLevel(BT_SECURITY_HIGH)) {
+ restartRequestTimer();
return true;
+ }
}
break;
default:
diff --git a/src/bluetooth/qlowenergycontroller_osx.mm b/src/bluetooth/qlowenergycontroller_osx.mm
index 80ef72af..a2923d81 100644
--- a/src/bluetooth/qlowenergycontroller_osx.mm
+++ b/src/bluetooth/qlowenergycontroller_osx.mm
@@ -1165,9 +1165,11 @@ void QLowEnergyController::disconnectFromDevice()
OSX_D_PTR;
- if (role() != CentralRole) {
- qCWarning(QT_BT_OSX) << "can not disconnect while in central role";
- return osx_d_ptr->_q_CBManagerError(ConnectionError);
+ if (role() == PeripheralRole) {
+ // CoreBluetooth API intentionally does not provide any way of closing
+ // a connection. All we can do here is to stop the advertisement.
+ stopAdvertising();
+ return;
}
if (osx_d_ptr->isValid()) {
@@ -1309,7 +1311,7 @@ void QLowEnergyController::stopAdvertising()
return osx_d_ptr->_q_CBManagerError(UnknownError);
if (state() != AdvertisingState) {
- qCDebug(QT_BT_OSX) << "called in state" << state();
+ qCDebug(QT_BT_OSX) << "cannot stop advertising, called in state" << state();
return;
}
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index f6bf0ae3..19d10567 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -94,6 +94,7 @@ class QWinRTLowEnergyServiceHandler;
QT_BEGIN_NAMESPACE
class QLowEnergyServiceData;
+class QTimer;
#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE)
class HciManager;
@@ -276,6 +277,24 @@ private:
HciManager *hciManager;
QLeAdvertiser *advertiser;
QSocketNotifier *serverSocketNotifier;
+ QTimer *requestTimer = nullptr;
+
+ /*
+ Defines the maximum number of milliseconds the implementation will
+ wait for requests that require a response.
+
+ This addresses the problem that some non-conformant BTLE devices
+ do not implement the request/response system properly. In such cases
+ the queue system would hang forever.
+
+ Once timeout has been triggered we gracefully continue with the next request.
+ Depending on the type of the timed out ATT command we either ignore it
+ or artifically trigger an error response to ensure the API gives the
+ appropriate response. Potentially this can cause problems when the
+ response for the dropped requests arrives very late. That's why a big warning
+ is printed about the compromised state when a timeout is triggered.
+ */
+ int gattRequestTimeout = 20000;
void handleConnectionRequest();
void closeServerSocket();
@@ -392,12 +411,15 @@ private:
const QLowEnergyHandle descriptorHandle,
const QByteArray &newValue);
+ void restartRequestTimer();
+
private slots:
void l2cpConnected();
void l2cpDisconnected();
void l2cpErrorChanged(QBluetoothSocket::SocketError);
void l2cpReadyRead();
void encryptionChangedEvent(const QBluetoothAddress&, bool);
+ void handleGattRequestTimeout();
#elif defined(QT_ANDROID_BLUETOOTH)
LowEnergyNotificationHub *hub;
@@ -457,6 +479,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/",