summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@qt.io>2017-07-03 14:59:10 +0200
committerAlex Blasche <alexander.blasche@qt.io>2017-07-18 10:41:17 +0000
commitf9f112edf1ececb883438ee31c9fc8d960f40ab5 (patch)
tree23229e948170cbe5dd077e5bf1cc8f883b26c031 /src
parent746187ad9ef1f8c9a3d797879bb3771377ae4bca (diff)
Add ability to negotiate the MTU on BTLE Android
There are cases where peripherals use a larger MTU than the default MTU on Android. This forces the MTU negotiation to always been done once the service discovery has been done. This patch requires Android API v21 (or Android v5+). If the local Android version is below 5 this feature becomes a noop. The related bug cannot be addressed on Android version below 5.0. Task-number: QTBUG-61755 Change-Id: I6521b5dad05da5e3e533ef2af56ee649b1b79730 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java154
1 files changed, 125 insertions, 29 deletions
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 305359aa..b82a647c 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,6 +48,7 @@ import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -71,13 +72,21 @@ public class QtBluetoothLE {
private BluetoothGatt mBluetoothGatt = null;
private String mRemoteGattAddress;
private final UUID clientCharacteristicUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+ private final int MAX_MTU = 512;
+ private final int DEFAULT_MTU = 23;
+ private int mSupportedMtu = -1;
/*
* 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
+ // handle values above zero are for regular handle specific read/write requests
+ // handle values below zero are reserved for handle-independent requests
+ private int HANDLE_FOR_RESET = -1;
+ private int HANDLE_FOR_MTU_EXCHANGE = -2;
+ private AtomicInteger handleForTimeout = new AtomicInteger(HANDLE_FOR_RESET); // implies not running by default
+
private final int RUNNABLE_TIMEOUT = 3000; // 3 seconds
private final Handler timeoutHandler = new Handler(Looper.getMainLooper());
@@ -85,14 +94,18 @@ public class QtBluetoothLE {
public TimeoutRunnable(int handle) { pendingJobHandle = handle; }
@Override
public void run() {
- boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, -1);
+ boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, HANDLE_FOR_RESET);
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 " +
+ Log.w(TAG, "****** Looks like the peripheral does NOT act in " +
"accordance to Bluetooth 4.x spec.");
Log.w(TAG, "****** Please check server implementation. Continuing under " +
"reservation.");
- interruptCurrentIO(pendingJobHandle & 0xffff);
+
+ if (pendingJobHandle > HANDLE_FOR_RESET)
+ interruptCurrentIO(pendingJobHandle & 0xffff);
+ else if (pendingJobHandle < HANDLE_FOR_RESET)
+ interruptCurrentIO(pendingJobHandle);
}
}
@@ -217,6 +230,8 @@ public class QtBluetoothLE {
errorCode = status; break; //TODO deal with all errors
}
leServicesDiscovered(qtObject, errorCode, builder.toString());
+
+ scheduleMtuExchange();
}
public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
@@ -241,7 +256,7 @@ public class QtBluetoothLE {
}
boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1);
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET);
if (requestTimedOut) {
Log.w(TAG, "Late char read reply after timeout was hit for handle " + foundHandle);
// Timeout has hit before this response -> ignore the response
@@ -305,7 +320,7 @@ public class QtBluetoothLE {
}
boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(handle, IoJobType.Write), -1);
+ modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET);
if (requestTimedOut) {
Log.w(TAG, "Late char write reply after timeout was hit for handle " + handle);
// Timeout has hit before this response -> ignore the response
@@ -362,7 +377,7 @@ public class QtBluetoothLE {
}
boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(foundHandle, IoJobType.Read), -1);
+ modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET);
if (requestTimedOut) {
Log.w(TAG, "Late descriptor read reply after timeout was hit for handle " +
foundHandle);
@@ -435,7 +450,7 @@ public class QtBluetoothLE {
int handle = handleForDescriptor(descriptor);
boolean requestTimedOut = !handleForTimeout.compareAndSet(
- modifiedReadWriteHandle(handle, IoJobType.Write), -1);
+ modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET);
if (requestTimedOut) {
Log.w(TAG, "Late descriptor write reply after timeout was hit for handle " +
handle);
@@ -471,6 +486,32 @@ public class QtBluetoothLE {
// System.out.println("onReadRemoteRssi");
// }
+ // requires Android API v21
+ public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status)
+ {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Log.w(TAG, "MTU changed to " + mtu);
+ mSupportedMtu = mtu;
+ } else {
+ Log.w(TAG, "MTU change error " + status + ". New MTU " + mtu);
+ mSupportedMtu = DEFAULT_MTU;
+ }
+
+ boolean requestTimedOut = !handleForTimeout.compareAndSet(
+ modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET);
+ if (requestTimedOut) {
+ Log.w(TAG, "Late mtu reply after timeout was hit");
+ // Timeout has hit before this response -> ignore the response
+ // no need to unlock ioJobPending -> the timeout has done that already
+ return;
+ }
+
+ synchronized (readWriteQueue) {
+ ioJobPending = false;
+ }
+
+ performNextIO();
+ }
};
@@ -548,7 +589,7 @@ public class QtBluetoothLE {
private enum IoJobType
{
- Read, Write
+ Read, Write, Mtu
}
private class ReadWriteJob
@@ -724,7 +765,7 @@ public class QtBluetoothLE {
// kill all timeout handlers
timeoutHandler.removeCallbacksAndMessages(null);
- handleForTimeout.set(-1);
+ handleForTimeout.set(HANDLE_FOR_RESET);
synchronized (readWriteQueue) {
readWriteQueue.clear();
@@ -835,6 +876,42 @@ public class QtBluetoothLE {
handleDiscoveredService + 1, discoveredService.endHandle + 1);
}
+ private boolean executeMtuExchange()
+ {
+ if (Build.VERSION.SDK_INT >= 21) {
+ try {
+ Method mtuMethod = mBluetoothGatt.getClass().getDeclaredMethod("requestMtu", int.class);
+ if (mtuMethod != null) {
+ Boolean success = (Boolean) mtuMethod.invoke(mBluetoothGatt, MAX_MTU);
+ if (success.booleanValue()) {
+ Log.w(TAG, "MTU change initiated");
+ return false;
+ } else {
+ Log.w(TAG, "MTU change request failed");
+ }
+ }
+ } catch (Exception ex) {}
+ }
+
+ Log.w(TAG, "Assuming default MTU value of 23 bytes");
+
+ mSupportedMtu = DEFAULT_MTU;
+ return true;
+ }
+
+ private void scheduleMtuExchange()
+ {
+ ReadWriteJob newJob = new ReadWriteJob();
+ newJob.jobType = IoJobType.Mtu;
+ newJob.entry = null;
+
+ synchronized (readWriteQueue) {
+ readWriteQueue.add(newJob);
+ }
+
+ performNextIO();
+ }
+
/*
Internal Helper function for discoverServiceDetails()
@@ -1055,6 +1132,9 @@ public class QtBluetoothLE {
performNextIO();
+ if (handle == HANDLE_FOR_MTU_EXCHANGE)
+ return;
+
GattEntry entry = entries.get(handle);
if (entry == null)
return;
@@ -1079,24 +1159,28 @@ public class QtBluetoothLE {
boolean skip = false;
final ReadWriteJob nextJob;
- int handle = -1;
+ int handle = HANDLE_FOR_RESET;
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;
+ if (nextJob.jobType == IoJobType.Mtu) {
+ handle = HANDLE_FOR_MTU_EXCHANGE; //mtu request is special case
+ } else {
+ switch (nextJob.entry.type) {
+ case Characteristic:
+ handle = handleForCharacteristic(nextJob.entry.characteristic);
+ break;
+ case Descriptor:
+ handle = handleForDescriptor(nextJob.entry.descriptor);
+ break;
+ case CharacteristicValue:
+ handle = nextJob.entry.endHandle;
+ default:
+ break;
+ }
}
// timeout handler and handleForTimeout atomic must be setup before
@@ -1107,23 +1191,32 @@ public class QtBluetoothLE {
timeoutHandler.removeCallbacksAndMessages(null); // remove any timeout handlers
handleForTimeout.set(modifiedReadWriteHandle(handle, nextJob.jobType));
- if (nextJob.jobType == IoJobType.Read)
- skip = executeReadJob(nextJob);
- else
- skip = executeWriteJob(nextJob);
+ switch (nextJob.jobType) {
+ case Read:
+ skip = executeReadJob(nextJob);
+ break;
+ case Write:
+ skip = executeWriteJob(nextJob);
+ break;
+ case Mtu:
+ skip = executeMtuExchange();
+ break;
+ }
if (skip) {
- handleForTimeout.set(-1); // not a pending call -> release atomic
+ handleForTimeout.set(HANDLE_FOR_RESET); // 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 + " (" +
+ if (nextJob.jobType != IoJobType.Mtu) {
+ Log.w(TAG, "Performing queued job, handle: " + handle + " " + nextJob.jobType + " (" +
(nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
") ValueKnown: " + nextJob.entry.valueKnown + " Skipping: " + skip +
" " + nextJob.entry.type);
+ }
}
GattEntry entry = nextJob.entry;
@@ -1136,7 +1229,7 @@ public class QtBluetoothLE {
we have to report an error back to Qt. The error report is not required during
the initial service discovery though.
*/
- if (handle != -1) {
+ if (handle > HANDLE_FOR_RESET) {
// during service discovery we do not report error but emit characteristicRead()
// any other time a failure emits serviceError() signal
@@ -1326,6 +1419,9 @@ public class QtBluetoothLE {
case Read:
modifiedHandle = (modifiedHandle | 0x00020000);
break;
+ case Mtu:
+ modifiedHandle = HANDLE_FOR_MTU_EXCHANGE;
+ break;
}
return modifiedHandle;