@capacitor-community/bluetooth-le 7.1.1 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -161
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +47 -21
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +87 -36
- package/dist/docs.json +957 -890
- package/dist/esm/bleClient.d.ts +3 -2
- package/dist/esm/bleClient.js +18 -6
- package/dist/esm/bleClient.js.map +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/conversion.d.ts +12 -0
- package/dist/esm/conversion.js +33 -0
- package/dist/esm/conversion.js.map +1 -1
- package/dist/esm/definitions.d.ts +42 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/queue.js.map +1 -1
- package/dist/esm/timeout.js.map +1 -1
- package/dist/esm/validators.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +66 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +118 -8
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +118 -8
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/DeviceListView.swift +121 -0
- package/ios/Plugin/DeviceManager.swift +114 -13
- package/ios/Plugin/Plugin.swift +61 -6
- package/package.json +13 -3
|
@@ -21,7 +21,9 @@ import android.os.HandlerThread
|
|
|
21
21
|
import android.os.Looper
|
|
22
22
|
import androidx.annotation.RequiresApi
|
|
23
23
|
import com.getcapacitor.Logger
|
|
24
|
+
import java.util.Collections
|
|
24
25
|
import java.util.UUID
|
|
26
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
25
27
|
import java.util.concurrent.ConcurrentLinkedQueue
|
|
26
28
|
|
|
27
29
|
class CallbackResponse(
|
|
@@ -70,6 +72,7 @@ class Device(
|
|
|
70
72
|
private var callbackMap = HashMap<String, ((CallbackResponse) -> Unit)>()
|
|
71
73
|
private val timeoutQueue = ConcurrentLinkedQueue<TimeoutHandler>()
|
|
72
74
|
private var bondStateReceiver: BroadcastReceiver? = null
|
|
75
|
+
private val pendingBondKeys = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>())
|
|
73
76
|
private var currentMtu = -1
|
|
74
77
|
|
|
75
78
|
private lateinit var callbacksHandlerThread: HandlerThread
|
|
@@ -91,6 +94,22 @@ class Device(
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
fun cleanup() {
|
|
98
|
+
synchronized(this) {
|
|
99
|
+
bondStateReceiver?.let { receiver ->
|
|
100
|
+
try {
|
|
101
|
+
context.unregisterReceiver(receiver)
|
|
102
|
+
} catch (e: IllegalArgumentException) {
|
|
103
|
+
Logger.debug(TAG, "Bond state receiver already unregistered")
|
|
104
|
+
}
|
|
105
|
+
bondStateReceiver = null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pendingBondKeys.clear()
|
|
109
|
+
cleanupCallbacksHandlerThread()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
private val gattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
|
|
95
114
|
override fun onConnectionStateChange(
|
|
96
115
|
gatt: BluetoothGatt, status: Int, newState: Int
|
|
@@ -109,7 +128,7 @@ class Device(
|
|
|
109
128
|
bluetoothGatt?.close()
|
|
110
129
|
bluetoothGatt = null
|
|
111
130
|
Logger.debug(TAG, "Disconnected from GATT server.")
|
|
112
|
-
|
|
131
|
+
cleanup()
|
|
113
132
|
resolve("disconnect", "Disconnected.")
|
|
114
133
|
}
|
|
115
134
|
}
|
|
@@ -358,9 +377,16 @@ class Device(
|
|
|
358
377
|
fun createBond(timeout: Long, callback: (CallbackResponse) -> Unit) {
|
|
359
378
|
val key = "createBond"
|
|
360
379
|
callbackMap[key] = callback
|
|
380
|
+
|
|
381
|
+
// Check if already bonded first to avoid race condition
|
|
382
|
+
if (isBonded()) {
|
|
383
|
+
resolve(key, "Creating bond succeeded.")
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
361
387
|
try {
|
|
362
|
-
|
|
363
|
-
} catch (e:
|
|
388
|
+
ensureBondStateReceiverRegistered()
|
|
389
|
+
} catch (e: Exception) {
|
|
364
390
|
Logger.error(TAG, "Error while registering bondStateReceiver: ${e.localizedMessage}", e)
|
|
365
391
|
reject(key, "Creating bond failed.")
|
|
366
392
|
return
|
|
@@ -370,22 +396,17 @@ class Device(
|
|
|
370
396
|
reject(key, "Creating bond failed.")
|
|
371
397
|
return
|
|
372
398
|
}
|
|
373
|
-
//
|
|
374
|
-
if (isBonded()) {
|
|
375
|
-
resolve(key, "Creating bond succeeded.")
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
// otherwise, wait for bond state change
|
|
399
|
+
// Wait for bond state change
|
|
379
400
|
setTimeout(key, "Bonding timeout.", timeout)
|
|
380
401
|
}
|
|
381
402
|
|
|
382
|
-
private fun
|
|
383
|
-
|
|
403
|
+
private fun ensureBondStateReceiverRegistered() {
|
|
404
|
+
synchronized(this) {
|
|
405
|
+
if (bondStateReceiver != null) return
|
|
406
|
+
|
|
384
407
|
bondStateReceiver = object : BroadcastReceiver() {
|
|
385
|
-
override fun onReceive(
|
|
386
|
-
|
|
387
|
-
if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
|
|
388
|
-
val key = "createBond"
|
|
408
|
+
override fun onReceive(ctx: Context, intent: Intent) {
|
|
409
|
+
if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
|
|
389
410
|
val updatedDevice =
|
|
390
411
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
391
412
|
intent.getParcelableExtra(
|
|
@@ -395,27 +416,44 @@ class Device(
|
|
|
395
416
|
} else {
|
|
396
417
|
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
|
397
418
|
}
|
|
419
|
+
|
|
398
420
|
// BroadcastReceiver receives bond state updates from all devices, need to filter by device
|
|
399
421
|
if (device.address == updatedDevice?.address) {
|
|
400
|
-
val
|
|
401
|
-
val
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
422
|
+
val prev = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
|
|
423
|
+
val state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
|
|
424
|
+
Logger.debug(TAG, "Bond state transition $prev -> $state")
|
|
425
|
+
|
|
426
|
+
// Handle createBond callback
|
|
427
|
+
if (callbackMap.containsKey("createBond")) {
|
|
428
|
+
if (state == BluetoothDevice.BOND_BONDED) {
|
|
429
|
+
resolve("createBond", "Creating bond succeeded.")
|
|
430
|
+
} else if (prev == BluetoothDevice.BOND_BONDING && state == BluetoothDevice.BOND_NONE) {
|
|
431
|
+
reject("createBond", "Creating bond failed.")
|
|
432
|
+
} else if (state == -1) {
|
|
433
|
+
reject("createBond", "Creating bond failed.")
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Handle setNotifications callbacks (only for operations waiting on bonding)
|
|
438
|
+
if (prev == BluetoothDevice.BOND_BONDING && state == BluetoothDevice.BOND_NONE) {
|
|
439
|
+
val keysToReject = pendingBondKeys.toList()
|
|
440
|
+
pendingBondKeys.clear()
|
|
441
|
+
keysToReject.forEach { key ->
|
|
442
|
+
reject(key, "Pairing request was cancelled by the user.")
|
|
443
|
+
}
|
|
412
444
|
}
|
|
413
445
|
}
|
|
414
446
|
}
|
|
415
447
|
}
|
|
416
448
|
}
|
|
417
|
-
|
|
418
|
-
|
|
449
|
+
try {
|
|
450
|
+
val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
|
|
451
|
+
context.registerReceiver(bondStateReceiver, intentFilter)
|
|
452
|
+
} catch (e: Exception) {
|
|
453
|
+
Logger.error(TAG, "Error registering bond state receiver: ${e.localizedMessage}", e)
|
|
454
|
+
bondStateReceiver = null
|
|
455
|
+
throw e
|
|
456
|
+
}
|
|
419
457
|
}
|
|
420
458
|
}
|
|
421
459
|
|
|
@@ -546,6 +584,7 @@ class Device(
|
|
|
546
584
|
characteristicUUID: UUID,
|
|
547
585
|
enable: Boolean,
|
|
548
586
|
notifyCallback: ((CallbackResponse) -> Unit)?,
|
|
587
|
+
timeout: Long,
|
|
549
588
|
callback: (CallbackResponse) -> Unit,
|
|
550
589
|
) {
|
|
551
590
|
val key = "writeDescriptor|$serviceUUID|$characteristicUUID|$CLIENT_CHARACTERISTIC_CONFIG"
|
|
@@ -585,6 +624,19 @@ class Device(
|
|
|
585
624
|
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
|
|
586
625
|
}
|
|
587
626
|
|
|
627
|
+
// Track this operation as potentially needing bonding
|
|
628
|
+
if (!isBonded()) {
|
|
629
|
+
try {
|
|
630
|
+
ensureBondStateReceiverRegistered()
|
|
631
|
+
pendingBondKeys.add(key)
|
|
632
|
+
} catch (e: Exception) {
|
|
633
|
+
// Don't fail the notification attempt just because bonding
|
|
634
|
+
// can't be tracked. The call will still timeout if bonding is
|
|
635
|
+
// required for some reason
|
|
636
|
+
Logger.warn(TAG, "Error while registering bondStateReceiver: ${e.localizedMessage}")
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
588
640
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
589
641
|
val statusCode = bluetoothGatt?.writeDescriptor(descriptor, value)
|
|
590
642
|
if (statusCode != BluetoothStatusCodes.SUCCESS) {
|
|
@@ -600,6 +652,7 @@ class Device(
|
|
|
600
652
|
}
|
|
601
653
|
|
|
602
654
|
}
|
|
655
|
+
setTimeout(key, "Setting notification timeout.", timeout)
|
|
603
656
|
// wait for onDescriptorWrite
|
|
604
657
|
}
|
|
605
658
|
|
|
@@ -672,21 +725,19 @@ class Device(
|
|
|
672
725
|
}
|
|
673
726
|
|
|
674
727
|
private fun resolve(key: String, value: String) {
|
|
675
|
-
|
|
728
|
+
pendingBondKeys.remove(key)
|
|
729
|
+
callbackMap.remove(key)?.let { callback ->
|
|
676
730
|
Logger.debug(TAG, "resolve: $key $value")
|
|
677
731
|
timeoutQueue.popFirstMatch { it.key == key }?.handler?.removeCallbacksAndMessages(null)
|
|
678
|
-
val callback = callbackMap[key]
|
|
679
|
-
callbackMap.remove(key)
|
|
680
732
|
callback?.invoke(CallbackResponse(true, value))
|
|
681
733
|
}
|
|
682
734
|
}
|
|
683
735
|
|
|
684
736
|
private fun reject(key: String, value: String) {
|
|
685
|
-
|
|
737
|
+
pendingBondKeys.remove(key)
|
|
738
|
+
callbackMap.remove(key)?.let { callback ->
|
|
686
739
|
Logger.debug(TAG, "reject: $key $value")
|
|
687
740
|
timeoutQueue.popFirstMatch { it.key == key }?.handler?.removeCallbacksAndMessages(null)
|
|
688
|
-
val callback = callbackMap[key]
|
|
689
|
-
callbackMap.remove(key)
|
|
690
741
|
callback?.invoke(CallbackResponse(false, value))
|
|
691
742
|
}
|
|
692
743
|
}
|
|
@@ -713,7 +764,7 @@ class Device(
|
|
|
713
764
|
connectionState = STATE_DISCONNECTED
|
|
714
765
|
gatt?.disconnect()
|
|
715
766
|
gatt?.close()
|
|
716
|
-
|
|
767
|
+
cleanup()
|
|
717
768
|
reject(key, message)
|
|
718
769
|
}, timeout)
|
|
719
770
|
}
|