diff options
author | Konstantin Ritt <ritt.ks@gmail.com> | 2020-03-13 14:39:58 +0300 |
---|---|---|
committer | Konstantin Ritt <ritt.ks@gmail.com> | 2020-06-04 11:50:50 +0000 |
commit | 2f46485868a5d9f9a9035db7157864a3f5949a8a (patch) | |
tree | d4d15a4e897204a30a7779a12f6dafeac9c466b1 | |
parent | ac95e67f071e7ee8fb1d65d1a4fe1b68ceee7c2d (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.java | 66 |
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; } |