@capacitor-community/bluetooth-le 3.0.0 → 3.0.2

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 CHANGED
@@ -13,7 +13,7 @@
13
13
  <a href="https://www.npmjs.com/package/@capacitor-community/bluetooth-le"><img src="https://img.shields.io/npm/dw/@capacitor-community/bluetooth-le?style=flat-square" /></a>
14
14
  <a href="https://www.npmjs.com/package/@capacitor-community/bluetooth-le"><img src="https://img.shields.io/npm/v/@capacitor-community/bluetooth-le?style=flat-square" /></a>
15
15
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
16
- <a href="#contributors-"><img src="https://img.shields.io/badge/all%20contributors-10-orange?style=flat-square" /></a>
16
+ <a href="#contributors-"><img src="https://img.shields.io/badge/all%20contributors-14-orange?style=flat-square" /></a>
17
17
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
18
18
  </p>
19
19
 
@@ -122,6 +122,8 @@ If the app needs to use Bluetooth while it is in the background, you also have t
122
122
 
123
123
  ```
124
124
 
125
+ **Note**: Bluetooth is **not** available in the iOS simulator. The `initialize` call will be rejected with an error "BLE unsupported". You have to test your app on a real device.
126
+
125
127
  ### Android
126
128
 
127
129
  On Android, no further steps are required to use the plugin (if you are using Capacitor 2, see [here](https://github.com/capacitor-community/bluetooth-le/blob/0.x/README.md#android)).
@@ -205,6 +207,8 @@ import { BleClient } from '@capacitor-community/bluetooth-le';
205
207
  import { BluetoothLe } from '@capacitor-community/bluetooth-le';
206
208
  ```
207
209
 
210
+ ### Heart rate monitor
211
+
208
212
  Here is an example of how to use the plugin. It shows how to read the heart rate from a BLE heart rate monitor such as the Polar H10.
209
213
 
210
214
  ```typescript
@@ -277,7 +281,9 @@ function parseHeartRate(value: DataView): number {
277
281
  }
278
282
  ```
279
283
 
280
- An example of using the scanning API:
284
+ ### Scanning API
285
+
286
+ Here is an example of using the scanning API.
281
287
 
282
288
  ```typescript
283
289
  import { BleClient, numberToUUID } from '@capacitor-community/bluetooth-le';
@@ -401,6 +407,7 @@ enable() => Promise<void>
401
407
 
402
408
  Enable Bluetooth.
403
409
  Only available on **Android**.
410
+ _deprecated_ See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#enable()
404
411
 
405
412
  ---
406
413
 
@@ -412,6 +419,7 @@ disable() => Promise<void>
412
419
 
413
420
  Disable Bluetooth.
414
421
  Only available on **Android**.
422
+ _deprecated_ See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#disable()
415
423
 
416
424
  ---
417
425
 
@@ -528,7 +536,7 @@ requestLEScan(options: RequestBleDeviceOptions, callback: (result: ScanResult) =
528
536
 
529
537
  Start scanning for BLE devices to interact with according to the filters in the options. The callback will be invoked on each device that is found.
530
538
  Scanning will continue until `stopLEScan` is called. For an example, see [usage](#usage).
531
- **NOTE**: Use with care on **web** platform, the required API is still behind a flag in most browsers.
539
+ **Note**: Use with care on **web** platform, the required API is still behind a flag in most browsers.
532
540
 
533
541
  | Param | Type |
534
542
  | -------------- | --------------------------------------------------------------------------- |
@@ -604,15 +612,16 @@ Connect to a peripheral BLE device. For an example, see [usage](#usage).
604
612
  ### createBond(...)
605
613
 
606
614
  ```typescript
607
- createBond(deviceId: string) => Promise<void>
615
+ createBond(deviceId: string, options?: TimeoutOptions | undefined) => Promise<void>
608
616
  ```
609
617
 
610
618
  Create a bond with a peripheral BLE device.
611
619
  Only available on **Android**. On iOS bonding is handled by the OS.
612
620
 
613
- | Param | Type | Description |
614
- | -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- |
615
- | **`deviceId`** | <code>string</code> | The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan)) |
621
+ | Param | Type | Description |
622
+ | -------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
623
+ | **`deviceId`** | <code>string</code> | The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan)) |
624
+ | **`options`** | <code><a href="#timeoutoptions">TimeoutOptions</a></code> | Options for plugin call |
616
625
 
617
626
  ---
618
627
 
@@ -1042,6 +1051,23 @@ await BleClient.disconnect(device.deviceId);
1042
1051
  await BleClient.connect(device.deviceId);
1043
1052
  ```
1044
1053
 
1054
+ #### No devices found on Android
1055
+
1056
+ On Android, the `initialize` call requests the location permission. However, if location services are disable on the OS level, the app will not find any devices. You can check if the location is enabled and open the settings when not.
1057
+
1058
+ ```typescript
1059
+ async function initialize() {
1060
+ // Check if location is enabled
1061
+ if (this.platform.is('android')) {
1062
+ const isLocationEnabled = await BleClient.isLocationEnabled();
1063
+ if (!isLocationEnabled) {
1064
+ await BleClient.openLocationSettings();
1065
+ }
1066
+ }
1067
+ await BleClient.initialize();
1068
+ }
1069
+ ```
1070
+
1045
1071
  ## Contributors ✨
1046
1072
 
1047
1073
  Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -1064,6 +1090,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
1064
1090
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/jrobeson"><img src="https://avatars.githubusercontent.com/u/56908?v=4?s=100" width="100px;" alt="Johnny Robeson"/><br /><sub><b>Johnny Robeson</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=jrobeson" title="Code">💻</a></td>
1065
1091
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/aadito123"><img src="https://avatars.githubusercontent.com/u/63646058?v=4?s=100" width="100px;" alt="Aadit Olkar"/><br /><sub><b>Aadit Olkar</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=aadito123" title="Code">💻</a></td>
1066
1092
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/y3nd"><img src="https://avatars.githubusercontent.com/u/18102153?v=4?s=100" width="100px;" alt="Yoann N."/><br /><sub><b>Yoann N.</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=y3nd" title="Code">💻</a></td>
1093
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Andy3189"><img src="https://avatars.githubusercontent.com/u/2084016?v=4?s=100" width="100px;" alt="Andy3189"/><br /><sub><b>Andy3189</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=Andy3189" title="Code">💻</a></td>
1094
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/RFM69CW"><img src="https://avatars.githubusercontent.com/u/20404734?v=4?s=100" width="100px;" alt="Sammy"/><br /><sub><b>Sammy</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=RFM69CW" title="Documentation">📖</a></td>
1095
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/td-tomasz-joniec"><img src="https://avatars.githubusercontent.com/u/109506928?v=4?s=100" width="100px;" alt="td-tomasz-joniec"/><br /><sub><b>td-tomasz-joniec</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=td-tomasz-joniec" title="Code">💻</a></td>
1096
+ <td align="center" valign="top" width="14.28%"><a href="https://fanxj.com"><img src="https://avatars.githubusercontent.com/u/10436013?v=4?s=100" width="100px;" alt="Michele Ferrari"/><br /><sub><b>Michele Ferrari</b></sub></a><br /><a href="https://github.com/capacitor-community/bluetooth-le/commits?author=micheleypf" title="Code">💻</a></td>
1067
1097
  </tr>
1068
1098
  </tbody>
1069
1099
  </table>
@@ -1,7 +1,13 @@
1
1
  package com.capacitorjs.community.plugins.bluetoothle
2
2
 
3
3
  import android.Manifest
4
- import android.bluetooth.*
4
+ import android.annotation.SuppressLint
5
+ import android.bluetooth.BluetoothAdapter
6
+ import android.bluetooth.BluetoothDevice
7
+ import android.bluetooth.BluetoothGatt
8
+ import android.bluetooth.BluetoothGattCharacteristic
9
+ import android.bluetooth.BluetoothManager
10
+ import android.bluetooth.BluetoothProfile
5
11
  import android.bluetooth.le.ScanFilter
6
12
  import android.bluetooth.le.ScanResult
7
13
  import android.bluetooth.le.ScanSettings
@@ -14,15 +20,24 @@ import android.location.LocationManager
14
20
  import android.net.Uri
15
21
  import android.os.Build
16
22
  import android.os.ParcelUuid
17
- import android.provider.Settings.*
23
+ import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
24
+ import android.provider.Settings.ACTION_BLUETOOTH_SETTINGS
25
+ import android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS
18
26
  import androidx.core.location.LocationManagerCompat
19
- import com.getcapacitor.*
27
+ import com.getcapacitor.JSArray
28
+ import com.getcapacitor.JSObject
29
+ import com.getcapacitor.Logger
30
+ import com.getcapacitor.PermissionState
31
+ import com.getcapacitor.Plugin
32
+ import com.getcapacitor.PluginCall
33
+ import com.getcapacitor.PluginMethod
20
34
  import com.getcapacitor.annotation.CapacitorPlugin
21
35
  import com.getcapacitor.annotation.Permission
22
36
  import com.getcapacitor.annotation.PermissionCallback
23
- import java.util.*
37
+ import java.util.UUID
24
38
 
25
39
 
40
+ @SuppressLint("MissingPermission")
26
41
  @CapacitorPlugin(
27
42
  name = "BluetoothLe",
28
43
  permissions = [
@@ -83,8 +98,7 @@ class BluetoothLe : Plugin() {
83
98
 
84
99
  @PluginMethod
85
100
  fun initialize(call: PluginCall) {
86
- // Build.VERSION_CODES.S = 31
87
- if (Build.VERSION.SDK_INT >= 31) {
101
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
88
102
  val neverForLocation = call.getBoolean("androidNeverForLocation", false) as Boolean
89
103
  aliases = if (neverForLocation) {
90
104
  arrayOf(
@@ -426,7 +440,8 @@ class BluetoothLe : Plugin() {
426
440
  @PluginMethod
427
441
  fun createBond(call: PluginCall) {
428
442
  val device = getOrCreateDevice(call) ?: return
429
- device.createBond { response ->
443
+ val timeout = call.getFloat("timeout", DEFAULT_TIMEOUT)!!.toLong()
444
+ device.createBond(timeout) { response ->
430
445
  run {
431
446
  if (response.success) {
432
447
  call.resolve()
@@ -1,6 +1,16 @@
1
1
  package com.capacitorjs.community.plugins.bluetoothle
2
2
 
3
- import android.bluetooth.*
3
+ import android.annotation.SuppressLint
4
+ import android.annotation.TargetApi
5
+ import android.bluetooth.BluetoothAdapter
6
+ import android.bluetooth.BluetoothDevice
7
+ import android.bluetooth.BluetoothGatt
8
+ import android.bluetooth.BluetoothGattCallback
9
+ import android.bluetooth.BluetoothGattCharacteristic
10
+ import android.bluetooth.BluetoothGattDescriptor
11
+ import android.bluetooth.BluetoothGattService
12
+ import android.bluetooth.BluetoothProfile
13
+ import android.bluetooth.BluetoothStatusCodes
4
14
  import android.content.BroadcastReceiver
5
15
  import android.content.Context
6
16
  import android.content.Intent
@@ -8,14 +18,36 @@ import android.content.IntentFilter
8
18
  import android.os.Build
9
19
  import android.os.Handler
10
20
  import android.os.Looper
21
+ import androidx.annotation.RequiresApi
11
22
  import com.getcapacitor.Logger
12
- import java.util.*
23
+ import java.util.UUID
24
+ import java.util.concurrent.ConcurrentLinkedQueue
13
25
 
14
26
  class CallbackResponse(
15
27
  val success: Boolean,
16
28
  val value: String,
17
29
  )
18
30
 
31
+ class TimeoutHandler(
32
+ val key: String,
33
+ val handler: Handler
34
+ )
35
+
36
+ fun <T> ConcurrentLinkedQueue<T>.popFirstMatch(predicate: (T) -> Boolean): T? {
37
+ synchronized(this) {
38
+ val iterator = this.iterator()
39
+ while (iterator.hasNext()) {
40
+ val nextItem = iterator.next()
41
+ if (predicate(nextItem)) {
42
+ iterator.remove()
43
+ return nextItem
44
+ }
45
+ }
46
+ return null
47
+ }
48
+ }
49
+
50
+ @SuppressLint("MissingPermission")
19
51
  class Device(
20
52
  private val context: Context,
21
53
  bluetoothAdapter: BluetoothAdapter,
@@ -35,7 +67,7 @@ class Device(
35
67
  private var device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(address)
36
68
  private var bluetoothGatt: BluetoothGatt? = null
37
69
  private var callbackMap = HashMap<String, ((CallbackResponse) -> Unit)>()
38
- private var timeoutMap = HashMap<String, Handler>()
70
+ private val timeoutQueue = ConcurrentLinkedQueue<TimeoutHandler>()
39
71
  private var bondStateReceiver: BroadcastReceiver? = null
40
72
  private var currentMtu = -1
41
73
 
@@ -96,9 +128,15 @@ class Device(
96
128
  }
97
129
  }
98
130
 
131
+ @TargetApi(Build.VERSION_CODES.S_V2)
99
132
  override fun onCharacteristicRead(
100
133
  gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
101
134
  ) {
135
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
136
+ // handled by new callback below
137
+ return
138
+ }
139
+ Logger.verbose(TAG, "Using deprecated onCharacteristicRead.")
102
140
  super.onCharacteristicRead(gatt, characteristic, status)
103
141
  val key = "read|${characteristic.service.uuid}|${characteristic.uuid}"
104
142
  if (status == BluetoothGatt.GATT_SUCCESS) {
@@ -114,6 +152,24 @@ class Device(
114
152
  }
115
153
  }
116
154
 
155
+ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
156
+ override fun onCharacteristicRead(
157
+ gatt: BluetoothGatt,
158
+ characteristic: BluetoothGattCharacteristic,
159
+ data: ByteArray,
160
+ status: Int
161
+ ) {
162
+ Logger.verbose(TAG, "Using onCharacteristicRead from API level 33.")
163
+ super.onCharacteristicRead(gatt, characteristic, data, status)
164
+ val key = "read|${characteristic.service.uuid}|${characteristic.uuid}"
165
+ if (status == BluetoothGatt.GATT_SUCCESS) {
166
+ val value = bytesToString(data)
167
+ resolve(key, value)
168
+ } else {
169
+ reject(key, "Reading characteristic failed.")
170
+ }
171
+ }
172
+
117
173
  override fun onCharacteristicWrite(
118
174
  gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
119
175
  ) {
@@ -127,9 +183,15 @@ class Device(
127
183
 
128
184
  }
129
185
 
186
+ @TargetApi(Build.VERSION_CODES.S_V2)
130
187
  override fun onCharacteristicChanged(
131
188
  gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
132
189
  ) {
190
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
191
+ // handled by new callback below
192
+ return
193
+ }
194
+ Logger.verbose(TAG, "Using deprecated onCharacteristicChanged.")
133
195
  super.onCharacteristicChanged(gatt, characteristic)
134
196
  val notifyKey = "notification|${characteristic.service.uuid}|${characteristic.uuid}"
135
197
  val data = characteristic.value
@@ -139,9 +201,26 @@ class Device(
139
201
  }
140
202
  }
141
203
 
204
+ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
205
+ override fun onCharacteristicChanged(
206
+ gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray
207
+ ) {
208
+ Logger.verbose(TAG, "Using onCharacteristicChanged from API level 33.")
209
+ super.onCharacteristicChanged(gatt, characteristic, data)
210
+ val notifyKey = "notification|${characteristic.service.uuid}|${characteristic.uuid}"
211
+ val value = bytesToString(data)
212
+ callbackMap[notifyKey]?.invoke(CallbackResponse(true, value))
213
+ }
214
+
215
+ @TargetApi(Build.VERSION_CODES.S_V2)
142
216
  override fun onDescriptorRead(
143
217
  gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
144
218
  ) {
219
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
220
+ // handled by new callback below
221
+ return
222
+ }
223
+ Logger.verbose(TAG, "Using deprecated onDescriptorRead.")
145
224
  super.onDescriptorRead(gatt, descriptor, status)
146
225
  val key =
147
226
  "readDescriptor|${descriptor.characteristic.service.uuid}|${descriptor.characteristic.uuid}|${descriptor.uuid}"
@@ -158,6 +237,22 @@ class Device(
158
237
  }
159
238
  }
160
239
 
240
+ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
241
+ override fun onDescriptorRead(
242
+ gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, data: ByteArray
243
+ ) {
244
+ Logger.verbose(TAG, "Using onDescriptorRead from API level 33.")
245
+ super.onDescriptorRead(gatt, descriptor, status, data)
246
+ val key =
247
+ "readDescriptor|${descriptor.characteristic.service.uuid}|${descriptor.characteristic.uuid}|${descriptor.uuid}"
248
+ if (status == BluetoothGatt.GATT_SUCCESS) {
249
+ val value = bytesToString(data)
250
+ resolve(key, value)
251
+ } else {
252
+ reject(key, "Reading descriptor failed.")
253
+ }
254
+ }
255
+
161
256
  override fun onDescriptorWrite(
162
257
  gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
163
258
  ) {
@@ -229,7 +324,7 @@ class Device(
229
324
  return bluetoothGatt?.requestConnectionPriority(connectionPriority) ?: false
230
325
  }
231
326
 
232
- fun createBond(callback: (CallbackResponse) -> Unit) {
327
+ fun createBond(timeout: Long, callback: (CallbackResponse) -> Unit) {
233
328
  val key = "createBond"
234
329
  callbackMap[key] = callback
235
330
  try {
@@ -250,6 +345,7 @@ class Device(
250
345
  return
251
346
  }
252
347
  // otherwise, wait for bond state change
348
+ setTimeout(key, "Bonding timeout.", timeout)
253
349
  }
254
350
 
255
351
  private fun createBondStateReceiver() {
@@ -260,7 +356,14 @@ class Device(
260
356
  if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
261
357
  val key = "createBond"
262
358
  val updatedDevice =
263
- intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
359
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
360
+ intent.getParcelableExtra(
361
+ BluetoothDevice.EXTRA_DEVICE,
362
+ BluetoothDevice::class.java
363
+ )
364
+ } else {
365
+ intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
366
+ }
264
367
  // BroadcastReceiver receives bond state updates from all devices, need to filter by device
265
368
  if (device.address == updatedDevice?.address) {
266
369
  val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
@@ -388,12 +491,21 @@ class Device(
388
491
  return
389
492
  }
390
493
  val bytes = stringToBytes(value)
391
- characteristic.value = bytes
392
- characteristic.writeType = writeType
393
- val result = bluetoothGatt?.writeCharacteristic(characteristic)
394
- if (result != true) {
395
- reject(key, "Writing characteristic failed.")
396
- return
494
+
495
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
496
+ val statusCode = bluetoothGatt?.writeCharacteristic(characteristic, bytes, writeType)
497
+ if (statusCode != BluetoothStatusCodes.SUCCESS) {
498
+ reject(key, "Writing characteristic failed with status code $statusCode.")
499
+ return
500
+ }
501
+ } else {
502
+ characteristic.value = bytes
503
+ characteristic.writeType = writeType
504
+ val result = bluetoothGatt?.writeCharacteristic(characteristic)
505
+ if (result != true) {
506
+ reject(key, "Writing characteristic failed.")
507
+ return
508
+ }
397
509
  }
398
510
  setTimeout(key, "Write timeout.", timeout)
399
511
  }
@@ -430,20 +542,32 @@ class Device(
430
542
  return
431
543
  }
432
544
 
433
- if (enable) {
545
+ val value = if (enable) {
434
546
  if ((characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
435
- descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
547
+ BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
436
548
  } else if ((characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
437
- descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
549
+ BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
550
+ } else {
551
+ BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
438
552
  }
439
553
  } else {
440
- descriptor.value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
554
+ BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
441
555
  }
442
556
 
443
- val resultDesc = bluetoothGatt?.writeDescriptor(descriptor)
444
- if (resultDesc != true) {
445
- reject(key, "Setting notification failed.")
446
- return
557
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
558
+ val statusCode = bluetoothGatt?.writeDescriptor(descriptor, value)
559
+ if (statusCode != BluetoothStatusCodes.SUCCESS) {
560
+ reject(key, "Setting notification failed with status code $statusCode.")
561
+ return
562
+ }
563
+ } else {
564
+ descriptor.value = value
565
+ val resultDesc = bluetoothGatt?.writeDescriptor(descriptor)
566
+ if (resultDesc != true) {
567
+ reject(key, "Setting notification failed.")
568
+ return
569
+ }
570
+
447
571
  }
448
572
  // wait for onDescriptorWrite
449
573
  }
@@ -498,11 +622,20 @@ class Device(
498
622
  return
499
623
  }
500
624
  val bytes = stringToBytes(value)
501
- descriptor.value = bytes
502
- val result = bluetoothGatt?.writeDescriptor(descriptor)
503
- if (result != true) {
504
- reject(key, "Writing characteristic failed.")
505
- return
625
+
626
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
627
+ val statusCode = bluetoothGatt?.writeDescriptor(descriptor, bytes)
628
+ if (statusCode != BluetoothStatusCodes.SUCCESS) {
629
+ reject(key, "Writing descriptor failed with status code $statusCode.")
630
+ return
631
+ }
632
+ } else {
633
+ descriptor.value = bytes
634
+ val result = bluetoothGatt?.writeDescriptor(descriptor)
635
+ if (result != true) {
636
+ reject(key, "Writing descriptor failed.")
637
+ return
638
+ }
506
639
  }
507
640
  setTimeout(key, "Write timeout.", timeout)
508
641
  }
@@ -510,20 +643,18 @@ class Device(
510
643
  private fun resolve(key: String, value: String) {
511
644
  if (callbackMap.containsKey(key)) {
512
645
  Logger.debug(TAG, "resolve: $key $value")
646
+ timeoutQueue.popFirstMatch { it.key == key }?.handler?.removeCallbacksAndMessages(null)
513
647
  callbackMap[key]?.invoke(CallbackResponse(true, value))
514
648
  callbackMap.remove(key)
515
- timeoutMap[key]?.removeCallbacksAndMessages(null)
516
- timeoutMap.remove(key)
517
649
  }
518
650
  }
519
651
 
520
652
  private fun reject(key: String, value: String) {
521
653
  if (callbackMap.containsKey(key)) {
522
654
  Logger.debug(TAG, "reject: $key $value")
655
+ timeoutQueue.popFirstMatch { it.key == key }?.handler?.removeCallbacksAndMessages(null)
523
656
  callbackMap[key]?.invoke(CallbackResponse(false, value))
524
657
  callbackMap.remove(key)
525
- timeoutMap[key]?.removeCallbacksAndMessages(null)
526
- timeoutMap.remove(key)
527
658
  }
528
659
  }
529
660
 
@@ -531,7 +662,7 @@ class Device(
531
662
  key: String, message: String, timeout: Long
532
663
  ) {
533
664
  val handler = Handler(Looper.getMainLooper())
534
- timeoutMap[key] = handler
665
+ timeoutQueue.add(TimeoutHandler(key, handler))
535
666
  handler.postDelayed({
536
667
  reject(key, message)
537
668
  }, timeout)
@@ -544,7 +675,7 @@ class Device(
544
675
  timeout: Long,
545
676
  ) {
546
677
  val handler = Handler(Looper.getMainLooper())
547
- timeoutMap[key] = handler
678
+ timeoutQueue.add(TimeoutHandler(key, handler))
548
679
  handler.postDelayed({
549
680
  connectionState = STATE_DISCONNECTED
550
681
  gatt?.disconnect()
@@ -1,5 +1,6 @@
1
1
  package com.capacitorjs.community.plugins.bluetoothle
2
2
 
3
+ import android.annotation.SuppressLint
3
4
  import android.app.AlertDialog
4
5
  import android.bluetooth.BluetoothAdapter
5
6
  import android.bluetooth.BluetoothDevice
@@ -27,6 +28,7 @@ class DisplayStrings(
27
28
  val noDeviceFound: String,
28
29
  )
29
30
 
31
+ @SuppressLint("MissingPermission")
30
32
  class DeviceScanner(
31
33
  private val context: Context,
32
34
  bluetoothAdapter: BluetoothAdapter,
package/dist/docs.json CHANGED
@@ -38,8 +38,13 @@
38
38
  "signature": "() => Promise<void>",
39
39
  "parameters": [],
40
40
  "returns": "Promise<void>",
41
- "tags": [],
42
- "docs": "Enable Bluetooth.\nOnly available on **Android**.",
41
+ "tags": [
42
+ {
43
+ "name": "deprecated",
44
+ "text": "See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#enable()"
45
+ }
46
+ ],
47
+ "docs": "Enable Bluetooth.\nOnly available on **Android**.\n*deprecated* See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#enable()",
43
48
  "complexTypes": [],
44
49
  "slug": "enable"
45
50
  },
@@ -48,8 +53,13 @@
48
53
  "signature": "() => Promise<void>",
49
54
  "parameters": [],
50
55
  "returns": "Promise<void>",
51
- "tags": [],
52
- "docs": "Disable Bluetooth.\nOnly available on **Android**.",
56
+ "tags": [
57
+ {
58
+ "name": "deprecated",
59
+ "text": "See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#disable()"
60
+ }
61
+ ],
62
+ "docs": "Disable Bluetooth.\nOnly available on **Android**.\n*deprecated* See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#disable()",
53
63
  "complexTypes": [],
54
64
  "slug": "disable"
55
65
  },
@@ -197,7 +207,7 @@
197
207
  "text": "callback"
198
208
  }
199
209
  ],
200
- "docs": "Start scanning for BLE devices to interact with according to the filters in the options. The callback will be invoked on each device that is found.\nScanning will continue until `stopLEScan` is called. For an example, see [usage](#usage).\n**NOTE**: Use with care on **web** platform, the required API is still behind a flag in most browsers.",
210
+ "docs": "Start scanning for BLE devices to interact with according to the filters in the options. The callback will be invoked on each device that is found.\nScanning will continue until `stopLEScan` is called. For an example, see [usage](#usage).\n**Note**: Use with care on **web** platform, the required API is still behind a flag in most browsers.",
201
211
  "complexTypes": [
202
212
  "RequestBleDeviceOptions",
203
213
  "ScanResult"
@@ -303,12 +313,17 @@
303
313
  },
304
314
  {
305
315
  "name": "createBond",
306
- "signature": "(deviceId: string) => Promise<void>",
316
+ "signature": "(deviceId: string, options?: TimeoutOptions | undefined) => Promise<void>",
307
317
  "parameters": [
308
318
  {
309
319
  "name": "deviceId",
310
320
  "docs": "The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))",
311
321
  "type": "string"
322
+ },
323
+ {
324
+ "name": "options",
325
+ "docs": "Options for plugin call",
326
+ "type": "TimeoutOptions | undefined"
312
327
  }
313
328
  ],
314
329
  "returns": "Promise<void>",
@@ -316,10 +331,16 @@
316
331
  {
317
332
  "name": "param",
318
333
  "text": "deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))"
334
+ },
335
+ {
336
+ "name": "param",
337
+ "text": "options Options for plugin call"
319
338
  }
320
339
  ],
321
340
  "docs": "Create a bond with a peripheral BLE device.\nOnly available on **Android**. On iOS bonding is handled by the OS.",
322
- "complexTypes": [],
341
+ "complexTypes": [
342
+ "TimeoutOptions"
343
+ ],
323
344
  "slug": "createbond"
324
345
  },
325
346
  {
@@ -15,11 +15,15 @@ export interface BleClientInterface {
15
15
  /**
16
16
  * Enable Bluetooth.
17
17
  * Only available on **Android**.
18
+ * *deprecated* See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#enable()
19
+ * @deprecated See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#enable()
18
20
  */
19
21
  enable(): Promise<void>;
20
22
  /**
21
23
  * Disable Bluetooth.
22
24
  * Only available on **Android**.
25
+ * *deprecated* See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#disable()
26
+ * @deprecated See https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#disable()
23
27
  */
24
28
  disable(): Promise<void>;
25
29
  /**
@@ -69,7 +73,7 @@ export interface BleClientInterface {
69
73
  /**
70
74
  * Start scanning for BLE devices to interact with according to the filters in the options. The callback will be invoked on each device that is found.
71
75
  * Scanning will continue until `stopLEScan` is called. For an example, see [usage](#usage).
72
- * **NOTE**: Use with care on **web** platform, the required API is still behind a flag in most browsers.
76
+ * **Note**: Use with care on **web** platform, the required API is still behind a flag in most browsers.
73
77
  * @param options
74
78
  * @param callback
75
79
  */
@@ -105,8 +109,9 @@ export interface BleClientInterface {
105
109
  * Create a bond with a peripheral BLE device.
106
110
  * Only available on **Android**. On iOS bonding is handled by the OS.
107
111
  * @param deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))
112
+ * @param options Options for plugin call
108
113
  */
109
- createBond(deviceId: string): Promise<void>;
114
+ createBond(deviceId: string, options?: TimeoutOptions): Promise<void>;
110
115
  /**
111
116
  * Report whether a peripheral BLE device is bonded.
112
117
  * Only available on **Android**. On iOS bonding is handled by the OS.
@@ -237,7 +242,7 @@ declare class BleClientClass implements BleClientInterface {
237
242
  getDevices(deviceIds: string[]): Promise<BleDevice[]>;
238
243
  getConnectedDevices(services: string[]): Promise<BleDevice[]>;
239
244
  connect(deviceId: string, onDisconnect?: (deviceId: string) => void, options?: TimeoutOptions): Promise<void>;
240
- createBond(deviceId: string): Promise<void>;
245
+ createBond(deviceId: string, options?: TimeoutOptions): Promise<void>;
241
246
  isBonded(deviceId: string): Promise<boolean>;
242
247
  disconnect(deviceId: string): Promise<void>;
243
248
  getServices(deviceId: string): Promise<BleService[]>;
@@ -148,9 +148,9 @@ class BleClientClass {
148
148
  await BluetoothLe.connect(Object.assign({ deviceId }, options));
149
149
  });
150
150
  }
151
- async createBond(deviceId) {
151
+ async createBond(deviceId, options) {
152
152
  await this.queue(async () => {
153
- await BluetoothLe.createBond({ deviceId });
153
+ await BluetoothLe.createBond(Object.assign({ deviceId }, options));
154
154
  });
155
155
  }
156
156
  async isBonded(deviceId) {