Skip to content

Commit d539bda

Browse files
committed
feat: abort startNotifications on Android when bonding is cancelled
On Android, cancelling the OS pairing prompt for a BLE device did not interrupt the `startNotifications` call, potentially causing hangs. This update: - Detects when the user cancels bonding on Android - Aborts the `startNotifications` call in response - Aligns Android’s behaviour with the existing iOS implementation
1 parent e75b141 commit d539bda

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

  • android/src/main/java/com/capacitorjs/community/plugins/bluetoothle

android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Device(
6868
private var device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(address)
6969
private var bluetoothGatt: BluetoothGatt? = null
7070
private var callbackMap = HashMap<String, ((CallbackResponse) -> Unit)>()
71+
private val bondReceiverMap = HashMap<String, BroadcastReceiver>()
7172
private val timeoutQueue = ConcurrentLinkedQueue<TimeoutHandler>()
7273
private var bondStateReceiver: BroadcastReceiver? = null
7374
private var currentMtu = -1
@@ -280,6 +281,9 @@ class Device(
280281
super.onDescriptorWrite(gatt, descriptor, status)
281282
val key =
282283
"writeDescriptor|${descriptor.characteristic.service.uuid}|${descriptor.characteristic.uuid}|${descriptor.uuid}"
284+
bondReceiverMap.remove(key)?.let {
285+
context.unregisterReceiver(it)
286+
}
283287
if (status == BluetoothGatt.GATT_SUCCESS) {
284288
resolve(key, "Descriptor successfully written.")
285289
} else {
@@ -586,16 +590,57 @@ class Device(
586590
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
587591
}
588592

593+
val bondReceiver = object : BroadcastReceiver() {
594+
override fun onReceive(ctx: Context, intent: Intent) {
595+
if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
596+
val updatedDevice =
597+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
598+
intent.getParcelableExtra(
599+
BluetoothDevice.EXTRA_DEVICE,
600+
BluetoothDevice::class.java
601+
)
602+
} else {
603+
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
604+
}
605+
606+
// BroadcastReceiver receives bond state updates from all devices, need to filter by device
607+
if (device.address == updatedDevice?.address) {
608+
val prev = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
609+
val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
610+
if (state == BluetoothDevice.BOND_BONDED) {
611+
ctx.unregisterReceiver(this)
612+
} else if (prev == BluetoothDevice.BOND_BONDING && state == BluetoothDevice.BOND_NONE) {
613+
ctx.unregisterReceiver(this)
614+
reject(key, "Pairing request was cancelled by the user.")
615+
} else if (state == -1) {
616+
ctx.unregisterReceiver(this)
617+
}
618+
}
619+
}
620+
}
621+
}
622+
bondReceiverMap[key] = bondReceiver
623+
context.registerReceiver(
624+
bondReceiver,
625+
IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
626+
)
627+
589628
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
590629
val statusCode = bluetoothGatt?.writeDescriptor(descriptor, value)
591630
if (statusCode != BluetoothStatusCodes.SUCCESS) {
631+
bondReceiverMap.remove(key)?.let {
632+
context.unregisterReceiver(it)
633+
}
592634
reject(key, "Setting notification failed with status code $statusCode.")
593635
return
594636
}
595637
} else {
596638
descriptor.value = value
597639
val resultDesc = bluetoothGatt?.writeDescriptor(descriptor)
598640
if (resultDesc != true) {
641+
bondReceiverMap.remove(key)?.let {
642+
context.unregisterReceiver(it)
643+
}
599644
reject(key, "Setting notification failed.")
600645
return
601646
}

0 commit comments

Comments
 (0)