summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKonstantin Ritt <ritt.ks@gmail.com>2020-03-13 14:39:58 +0300
committerKonstantin Ritt <ritt.ks@gmail.com>2020-06-04 11:50:50 +0000
commit2f46485868a5d9f9a9035db7157864a3f5949a8a (patch)
treed4d15a4e897204a30a7779a12f6dafeac9c466b1
parentac95e67f071e7ee8fb1d65d1a4fe1b68ceee7c2d (diff)
Android: Make `Just Works` bonding really work
In case bond state has been changed due to access to a restricted handle, Android never completes the operation which triggered the devices to bind and thus never fires on(Characteristic|Descriptor)(Read|Write) callback, causing TimeoutRunnable to interrupt pending job, albeit the read/write job hasn't been actually executed by the peripheral; re-add the currently pending job to the queue's head and re-run it. Change-Id: Idd69c885a439a26e2819746d703a92778370e4ba Reviewed-by: Evgeniy Gagarin <eeiaao@gmail.com> Reviewed-by: Alex Blasche <alexander.blasche@qt.io> (cherry picked from commit b7b40e4222999be5eed5da3239886329c84bf731)
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java66
1 files changed, 66 insertions, 0 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 e9717a1d..96243af8 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
@@ -52,7 +52,10 @@ import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -125,6 +128,60 @@ public class QtBluetoothLE {
private int pendingJobHandle = -1;
};
+ // In case bond state has been changed due to access to a restricted handle,
+ // Android never completes the operation which triggered the devices to bind
+ // and thus never fires on(Characteristic|Descriptor)(Read|Write) callback,
+ // causing TimeoutRunnable to interrupt pending job,
+ // albeit the read/write job hasn't been actually executed by the peripheral;
+ // re-add the currently pending job to the queue's head and re-run it.
+ // If, by some reason, bonding process has been interrupted, either
+ // re-add the currently pending job to the queue's head and re-run it.
+ private class BondStateBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mBluetoothGatt == null)
+ return;
+
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
+ return;
+
+ final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ synchronized (readWriteQueue) {
+ if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
+ return;
+ }
+
+ timeoutHandler.removeCallbacksAndMessages(null);
+ handleForTimeout.set(HANDLE_FOR_RESET);
+ } else if (previousBondState == BluetoothDevice.BOND_BONDING && (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE)) {
+ synchronized (readWriteQueue) {
+ if (pendingJob == null || pendingJob.jobType == IoJobType.Mtu)
+ return;
+
+ readWriteQueue.addFirst(pendingJob);
+ pendingJob = null;
+ }
+
+ performNextIO();
+ } else if (previousBondState == BluetoothDevice.BOND_BONDED && bondState == BluetoothDevice.BOND_NONE) {
+ // peripheral or central removed the bond information;
+ // if it was peripheral, the connection attempt would fail with PIN_OR_KEY_MISSING,
+ // which is handled by Android by broadcasting ACTION_BOND_STATE_CHANGED
+ // with new state BOND_NONE, without actually deleting the bond information :facepalm:
+ // if we get there, it is safer to delete it now, by invoking the undocumented API call
+ try {
+ device.getClass().getMethod("removeBond").invoke(device);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+ };
+ private BroadcastReceiver bondStateBroadcastReceiver = null;
/* Pointer to the Qt object that "owns" the Java object */
@SuppressWarnings({"CanBeFinal", "WeakerAccess"})
@@ -218,6 +275,11 @@ public class QtBluetoothLE {
//This must be in sync with QLowEnergyController::ControllerState
switch (newState) {
case BluetoothProfile.STATE_DISCONNECTED:
+ if (bondStateBroadcastReceiver != null) {
+ qtContext.unregisterReceiver(bondStateBroadcastReceiver);
+ bondStateBroadcastReceiver = null;
+ }
+
qLowEnergyController_State = 0;
// we disconnected -> get rid of data from previous run
resetData();
@@ -233,6 +295,10 @@ public class QtBluetoothLE {
mBluetoothGatt = null;
break;
case BluetoothProfile.STATE_CONNECTED:
+ if (bondStateBroadcastReceiver == null) {
+ bondStateBroadcastReceiver = new BondStateBroadcastReceiver();
+ qtContext.registerReceiver(bondStateBroadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+ }
qLowEnergyController_State = 2;
}