react-native-ble-manager 12.3.0 → 12.4.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.
@@ -40,7 +40,7 @@ import java.util.List;
40
40
  import java.util.Map;
41
41
  import java.util.Set;
42
42
 
43
- class BleManager extends NativeBleManagerSpec {
43
+ public class BleManager extends NativeBleManagerSpec {
44
44
 
45
45
  public static final String LOG_TAG = "RNBleManager";
46
46
  private static final int ENABLE_REQUEST = 539;
@@ -282,6 +282,12 @@ class BleManager extends NativeBleManagerSpec {
282
282
  Log.d(LOG_TAG, "BleManager initialized");
283
283
  }
284
284
 
285
+ @ReactMethod
286
+ public void isStarted(Callback callback) {
287
+ Log.d(LOG_TAG, "isStarted");
288
+ callback.invoke(null, scanManager != null);
289
+ }
290
+
285
291
  @SuppressLint("MissingPermission")
286
292
  @ReactMethod
287
293
  public void enableBluetooth(Callback callback) {
@@ -310,7 +316,7 @@ class BleManager extends NativeBleManagerSpec {
310
316
  }
311
317
 
312
318
  @ReactMethod
313
- public void scan(ReadableArray serviceUUIDs, double timeoutSeconds, boolean allowDuplicates, ReadableMap scanningOptions,
319
+ public void scan(ReadableMap scanningOptions,
314
320
  Callback callback) {
315
321
  Log.d(LOG_TAG, "scan");
316
322
  if (getBluetoothAdapter() == null) {
@@ -333,7 +339,7 @@ class BleManager extends NativeBleManagerSpec {
333
339
  }
334
340
 
335
341
  if (scanManager != null)
336
- scanManager.scan(serviceUUIDs, (int) timeoutSeconds, scanningOptions, callback);
342
+ scanManager.scan(scanningOptions, callback);
337
343
  }
338
344
 
339
345
  @SuppressLint("NewApi") // NOTE: constructor checks the API version.
@@ -448,7 +454,7 @@ class BleManager extends NativeBleManagerSpec {
448
454
 
449
455
  @ReactMethod
450
456
  public void startNotificationWithBuffer(String deviceUUID, String serviceUUID, String characteristicUUID,
451
- double bufferLength, Callback callback) {
457
+ double bufferLength, Callback callback) {
452
458
  Log.d(LOG_TAG, "startNotification");
453
459
  if (serviceUUID == null || characteristicUUID == null) {
454
460
  callback.invoke("ServiceUUID and characteristicUUID required.");
@@ -1,16 +1,17 @@
1
1
  package it.innove;
2
2
 
3
+ import static it.innove.BleManager.LOG_TAG;
4
+
3
5
  import android.bluetooth.BluetoothDevice;
4
6
  import android.bluetooth.le.ScanRecord;
5
7
  import android.bluetooth.le.ScanResult;
6
8
  import android.os.Build;
7
9
  import android.os.ParcelUuid;
8
10
  import android.annotation.SuppressLint;
11
+ import android.util.Log;
9
12
  import android.util.SparseArray;
10
13
 
11
14
  import com.facebook.react.bridge.Arguments;
12
- import com.facebook.react.bridge.ReactApplicationContext;
13
- import com.facebook.react.bridge.ReactContext;
14
15
  import com.facebook.react.bridge.WritableArray;
15
16
  import com.facebook.react.bridge.WritableMap;
16
17
 
@@ -25,7 +26,7 @@ public class DefaultPeripheral extends Peripheral {
25
26
  private ScanResult scanResult;
26
27
 
27
28
  public DefaultPeripheral(BleManager bleManager, ScanResult result) {
28
- super(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes(), bleManager);
29
+ super(result.getDevice(), result.getRssi(), Objects.requireNonNull(result.getScanRecord()).getBytes(), bleManager);
29
30
  this.advertisingData = result.getScanRecord();
30
31
  this.scanResult = result;
31
32
  }
@@ -40,9 +41,9 @@ public class DefaultPeripheral extends Peripheral {
40
41
  WritableMap advertising = Arguments.createMap();
41
42
 
42
43
  try {
43
- String name = device.getName();
44
- if (name == null) {
45
- name = Objects.requireNonNull(scanResult.getScanRecord()).getDeviceName();
44
+ String name = getSafeDeviceName();
45
+ if (name == null && scanResult != null && scanResult.getScanRecord() != null) {
46
+ name = scanResult.getScanRecord().getDeviceName();
46
47
  }
47
48
  map.putString("name", name);
48
49
  map.putString("id", device.getAddress()); // mac address
@@ -50,7 +51,7 @@ public class DefaultPeripheral extends Peripheral {
50
51
 
51
52
  advertising.putMap("rawData", byteArrayToWritableMap(advertisingDataBytes));
52
53
 
53
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
54
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
54
55
  // We can check if peripheral is connectable using the scanresult
55
56
  if (this.scanResult != null) {
56
57
  advertising.putBoolean("isConnectable", scanResult.isConnectable());
@@ -66,7 +67,7 @@ public class DefaultPeripheral extends Peripheral {
66
67
  advertising.putString("localName", deviceName.replace("\0", ""));
67
68
 
68
69
  WritableArray serviceUuids = Arguments.createArray();
69
- if (advertisingData.getServiceUuids() != null && advertisingData.getServiceUuids().size() != 0) {
70
+ if (advertisingData.getServiceUuids() != null && !advertisingData.getServiceUuids().isEmpty()) {
70
71
  for (ParcelUuid uuid : advertisingData.getServiceUuids()) {
71
72
  serviceUuids.pushString(UUIDHelper.uuidToString(uuid.getUuid()));
72
73
  }
@@ -106,7 +107,7 @@ public class DefaultPeripheral extends Peripheral {
106
107
 
107
108
  map.putMap("advertising", advertising);
108
109
  } catch (Exception e) { // this shouldn't happen
109
- e.printStackTrace();
110
+ Log.e(LOG_TAG, "asWritableMap error", e);
110
111
  }
111
112
 
112
113
  return map;
@@ -115,6 +116,6 @@ public class DefaultPeripheral extends Peripheral {
115
116
  public void updateData(ScanResult result) {
116
117
  scanResult = result;
117
118
  advertisingData = result.getScanRecord();
118
- advertisingDataBytes = advertisingData.getBytes();
119
+ advertisingDataBytes = advertisingData != null ? advertisingData.getBytes() : null;
119
120
  }
120
121
  }
@@ -36,6 +36,7 @@ import com.facebook.react.bridge.WritableMap;
36
36
  import java.util.ArrayList;
37
37
  import java.util.Arrays;
38
38
  import java.util.List;
39
+ import java.util.Objects;
39
40
 
40
41
  @SuppressLint("MissingPermission")
41
42
  public class DefaultScanManager extends ScanManager {
@@ -65,7 +66,7 @@ public class DefaultScanManager extends ScanManager {
65
66
  }
66
67
 
67
68
  @Override
68
- public void scan(ReadableArray serviceUUIDs, final int scanSeconds, ReadableMap options, Callback callback) {
69
+ public void scan(ReadableMap options, Callback callback) {
69
70
  ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
70
71
  List<ScanFilter> filters = new ArrayList<>();
71
72
 
@@ -77,16 +78,14 @@ public class DefaultScanManager extends ScanManager {
77
78
  scanSettingsBuilder.setScanMode(options.getInt("scanMode"));
78
79
  }
79
80
 
80
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
81
- if (options.hasKey("numberOfMatches")) {
82
- scanSettingsBuilder.setNumOfMatches(options.getInt("numberOfMatches"));
83
- }
84
- if (options.hasKey("matchMode")) {
85
- scanSettingsBuilder.setMatchMode(options.getInt("matchMode"));
86
- }
87
- if (options.hasKey("callbackType")) {
88
- scanSettingsBuilder.setCallbackType(options.getInt("callbackType"));
89
- }
81
+ if (options.hasKey("numberOfMatches")) {
82
+ scanSettingsBuilder.setNumOfMatches(options.getInt("numberOfMatches"));
83
+ }
84
+ if (options.hasKey("matchMode")) {
85
+ scanSettingsBuilder.setMatchMode(options.getInt("matchMode"));
86
+ }
87
+ if (options.hasKey("callbackType")) {
88
+ scanSettingsBuilder.setCallbackType(options.getInt("callbackType"));
90
89
  }
91
90
 
92
91
  if (options.hasKey("reportDelay")) {
@@ -103,9 +102,10 @@ public class DefaultScanManager extends ScanManager {
103
102
  }
104
103
  }
105
104
 
106
- if (serviceUUIDs.size() > 0) {
105
+ ReadableArray serviceUUIDs = options.getArray("serviceUUIDs");
106
+ if (serviceUUIDs != null && serviceUUIDs.size() > 0) {
107
107
  for (int i = 0; i < serviceUUIDs.size(); i++) {
108
- ScanFilter filter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(UUIDHelper.uuidFromString(serviceUUIDs.getString(i)))).build();
108
+ ScanFilter filter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(UUIDHelper.uuidFromString(Objects.requireNonNull(serviceUUIDs.getString(i))))).build();
109
109
  filters.add(filter);
110
110
  Log.d(BleManager.LOG_TAG, "Filter service: " + serviceUUIDs.getString(i));
111
111
  }
@@ -221,6 +221,7 @@ public class DefaultScanManager extends ScanManager {
221
221
 
222
222
  isScanning = true;
223
223
 
224
+ long scanSeconds = (long) options.getDouble("seconds");
224
225
  if (scanSeconds > 0) {
225
226
  Thread thread = new Thread() {
226
227
  private final int currentScanSession = scanSessionId.incrementAndGet();
@@ -337,7 +338,9 @@ public class DefaultScanManager extends ScanManager {
337
338
  if (scanner != null) {
338
339
  try {
339
340
  if (scanningWithIntent && scanPendingIntent != null) {
340
- scanner.stopScan(scanPendingIntent);
341
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
342
+ scanner.stopScan(scanPendingIntent);
343
+ }
341
344
  } else {
342
345
  scanner.stopScan(mScanCallback);
343
346
  }
@@ -2,6 +2,7 @@ package it.innove;
2
2
 
3
3
  import static com.facebook.react.common.ReactConstants.TAG;
4
4
 
5
+ import android.Manifest;
5
6
  import android.annotation.SuppressLint;
6
7
  import android.app.Activity;
7
8
  import android.bluetooth.BluetoothDevice;
@@ -12,7 +13,7 @@ import android.bluetooth.BluetoothGattDescriptor;
12
13
  import android.bluetooth.BluetoothGattService;
13
14
  import android.bluetooth.BluetoothProfile;
14
15
  import android.bluetooth.BluetoothStatusCodes;
15
-
16
+ import android.content.pm.PackageManager;
16
17
  import android.content.Context;
17
18
  import android.os.Build;
18
19
  import android.os.Handler;
@@ -22,6 +23,7 @@ import android.util.Log;
22
23
 
23
24
  import androidx.annotation.NonNull;
24
25
  import androidx.annotation.Nullable;
26
+ import androidx.core.content.ContextCompat;
25
27
 
26
28
  import com.facebook.react.bridge.Arguments;
27
29
  import com.facebook.react.bridge.Callback;
@@ -79,7 +81,7 @@ public class Peripheral extends BluetoothGattCallback {
79
81
  private final Handler mainHandler = new Handler(Looper.getMainLooper());
80
82
  private boolean commandQueueBusy = false;
81
83
 
82
- private List<byte[]> writeQueue = new ArrayList<>();
84
+ private final Queue<byte[]> writeQueue = new LinkedList<>();
83
85
 
84
86
  public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, BleManager bleManager) {
85
87
  this.device = device;
@@ -160,10 +162,10 @@ public class Peripheral extends BluetoothGattCallback {
160
162
  // bt_btif : Register with GATT stack failed.
161
163
 
162
164
  public void disconnect(final Callback callback, final boolean force) {
165
+ connected = false;
163
166
  mainHandler.post(() -> {
164
167
  errorAndClearAllCallbacks("Disconnect called before the command completed");
165
168
  resetQueuesAndBuffers();
166
- connected = false;
167
169
 
168
170
  if (gatt != null) {
169
171
  try {
@@ -190,11 +192,11 @@ public class Peripheral extends BluetoothGattCallback {
190
192
  WritableMap advertising = Arguments.createMap();
191
193
 
192
194
  try {
193
- map.putString("name", device.getName());
195
+ String name = getSafeDeviceName();
196
+ map.putString("name", name);
194
197
  map.putString("id", device.getAddress()); // mac address
195
198
  map.putInt("rssi", advertisingRSSI);
196
199
 
197
- String name = device.getName();
198
200
  if (name != null)
199
201
  advertising.putString("localName", name);
200
202
 
@@ -211,6 +213,23 @@ public class Peripheral extends BluetoothGattCallback {
211
213
  return map;
212
214
  }
213
215
 
216
+ @Nullable
217
+ protected String getSafeDeviceName() {
218
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
219
+ Context context = bleManager != null ? bleManager.getReactContext() : null;
220
+ if (context == null) {
221
+ return null;
222
+ }
223
+
224
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT)
225
+ != PackageManager.PERMISSION_GRANTED) {
226
+ return null;
227
+ }
228
+ }
229
+
230
+ return device.getName();
231
+ }
232
+
214
233
  public WritableMap asWritableMap(BluetoothGatt gatt) {
215
234
 
216
235
  WritableMap map = asWritableMap();
@@ -219,13 +238,13 @@ public class Peripheral extends BluetoothGattCallback {
219
238
  WritableArray characteristicsArray = Arguments.createArray();
220
239
 
221
240
  if (connected && gatt != null) {
222
- for (Iterator<BluetoothGattService> it = gatt.getServices().iterator(); it.hasNext();) {
241
+ for (Iterator<BluetoothGattService> it = gatt.getServices().iterator(); it.hasNext(); ) {
223
242
  BluetoothGattService service = it.next();
224
243
  WritableMap serviceMap = Arguments.createMap();
225
244
  serviceMap.putString("uuid", UUIDHelper.uuidToString(service.getUuid()));
226
245
 
227
246
  for (Iterator<BluetoothGattCharacteristic> itCharacteristic = service.getCharacteristics()
228
- .iterator(); itCharacteristic.hasNext();) {
247
+ .iterator(); itCharacteristic.hasNext(); ) {
229
248
  BluetoothGattCharacteristic characteristic = itCharacteristic.next();
230
249
  WritableMap characteristicsMap = Arguments.createMap();
231
250
 
@@ -297,15 +316,12 @@ public class Peripheral extends BluetoothGattCallback {
297
316
  for (Callback retrieveServicesCallback : retrieveServicesCallbacks) {
298
317
  retrieveServicesCallback.invoke("Error during service retrieval: gatt is null");
299
318
  }
300
- }
301
- else if (status == BluetoothGatt.GATT_SUCCESS)
302
- {
319
+ } else if (status == BluetoothGatt.GATT_SUCCESS) {
303
320
  for (Callback retrieveServicesCallback : retrieveServicesCallbacks) {
304
321
  WritableMap map = this.asWritableMap(gatt);
305
322
  retrieveServicesCallback.invoke(null, map);
306
323
  }
307
- }
308
- else {
324
+ } else {
309
325
  for (Callback retrieveServicesCallback : retrieveServicesCallbacks) {
310
326
  retrieveServicesCallback.invoke("Error during service retrieval.");
311
327
  }
@@ -370,12 +386,20 @@ public class Peripheral extends BluetoothGattCallback {
370
386
  connected = false;
371
387
  clearBuffers();
372
388
  }
389
+
373
390
  @Override
374
391
  public void onConnectionStateChange(BluetoothGatt gatta, int status, final int newState) {
375
392
 
376
393
  Log.d(BleManager.LOG_TAG, "onConnectionStateChange to " + newState + " on peripheral: " + device.getAddress()
377
394
  + " with status " + status);
378
395
 
396
+ // We immediately update the internal connection status
397
+ if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
398
+ connected = true;
399
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED || status != BluetoothGatt.GATT_SUCCESS) {
400
+ connected = false;
401
+ }
402
+
379
403
  mainHandler.post(() -> {
380
404
  gatt = gatta;
381
405
 
@@ -385,8 +409,6 @@ public class Peripheral extends BluetoothGattCallback {
385
409
 
386
410
  connecting = false;
387
411
  if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
388
- connected = true;
389
-
390
412
  sendConnectionEvent(device, status);
391
413
 
392
414
  Log.d(BleManager.LOG_TAG, "Connected to: " + device.getAddress());
@@ -420,7 +442,7 @@ public class Peripheral extends BluetoothGattCallback {
420
442
  advertisingDataBytes = data;
421
443
  }
422
444
 
423
- //////
445
+ /// ///
424
446
 
425
447
  @Override
426
448
  public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
@@ -456,7 +478,7 @@ public class Peripheral extends BluetoothGattCallback {
456
478
  return;
457
479
  }
458
480
  }
459
-
481
+
460
482
  WritableMap map = Arguments.createMap();
461
483
  map.putString("peripheral", device.getAddress());
462
484
  map.putString("characteristic", charString);
@@ -480,6 +502,7 @@ public class Peripheral extends BluetoothGattCallback {
480
502
  onCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
481
503
  }
482
504
  }
505
+
483
506
  @Override
484
507
  public void onCharacteristicRead(@NonNull final BluetoothGatt gatt,
485
508
  @NonNull final BluetoothGattCharacteristic characteristic,
@@ -517,27 +540,28 @@ public class Peripheral extends BluetoothGattCallback {
517
540
  super.onCharacteristicWrite(gatt, characteristic, status);
518
541
 
519
542
  mainHandler.post(() -> {
520
- if (writeQueue.size() > 0) {
521
- byte[] data = writeQueue.get(0);
522
- writeQueue.remove(0);
523
- doWrite(characteristic, data, null);
524
- } else if (status != BluetoothGatt.GATT_SUCCESS) {
525
- if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
526
- Log.d(BleManager.LOG_TAG, "Write needs bonding");
527
- // *not* doing completedCommand()
528
- return;
529
- }
530
- for (Callback writeCallback : writeCallbacks) {
531
- writeCallback.invoke("Error writing " + characteristic.getUuid() + " status=" + status, null);
532
- }
533
- writeCallbacks.clear();
534
- } else if (!writeCallbacks.isEmpty()) {
535
- for (Callback writeCallback : writeCallbacks) {
536
- writeCallback.invoke();
543
+ if (!writeQueue.isEmpty()) {
544
+ byte[] data = writeQueue.poll();
545
+ doWrite(characteristic, data);
546
+ } else {
547
+ if (status != BluetoothGatt.GATT_SUCCESS) {
548
+ if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
549
+ Log.d(BleManager.LOG_TAG, "Write needs bonding");
550
+ // *not* doing completedCommand()
551
+ return;
552
+ }
553
+ for (Callback writeCallback : writeCallbacks) {
554
+ writeCallback.invoke("Error writing " + characteristic.getUuid() + " status=" + status, null);
555
+ }
556
+ writeCallbacks.clear();
557
+ } else if (!writeCallbacks.isEmpty()) {
558
+ for (Callback writeCallback : writeCallbacks) {
559
+ writeCallback.invoke();
560
+ }
561
+ writeCallbacks.clear();
537
562
  }
538
- writeCallbacks.clear();
563
+ completedCommand();
539
564
  }
540
- completedCommand();
541
565
  });
542
566
  }
543
567
 
@@ -699,7 +723,7 @@ public class Peripheral extends BluetoothGattCallback {
699
723
  // Then write to descriptor
700
724
  registerNotifyCallbacks.addLast(callback);
701
725
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
702
- result &= BluetoothStatusCodes.SUCCESS == gatt.writeDescriptor(descriptor, finalValue);
726
+ result &= BluetoothStatusCodes.SUCCESS == gatt.writeDescriptor(descriptor, finalValue);
703
727
  } else {
704
728
  descriptor.setValue(finalValue);
705
729
  result &= gatt.writeDescriptor(descriptor);
@@ -752,7 +776,7 @@ public class Peripheral extends BluetoothGattCallback {
752
776
  // until we find the best match
753
777
  // This function prefers Notify over Indicate
754
778
  private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattService service,
755
- UUID characteristicUUID) {
779
+ UUID characteristicUUID) {
756
780
 
757
781
  try {
758
782
  // Check for Notify first
@@ -810,7 +834,7 @@ public class Peripheral extends BluetoothGattCallback {
810
834
  }
811
835
 
812
836
  public void readDescriptor(UUID serviceUUID, UUID characteristicUUID, UUID descriptorUUID,
813
- final Callback callback) {
837
+ final Callback callback) {
814
838
  enqueue(() -> {
815
839
  if (!isConnected() || gatt == null) {
816
840
  callback.invoke("Device is not connected", null);
@@ -893,9 +917,8 @@ public class Peripheral extends BluetoothGattCallback {
893
917
  writeCallback.invoke("writeDescriptor failed for descriptor: " + descriptor.getUuid(), null);
894
918
  }
895
919
  writeDescriptorCallbacks.clear();
920
+ completedCommand();
896
921
  }
897
-
898
- completedCommand();
899
922
  });
900
923
  }
901
924
 
@@ -939,14 +962,6 @@ public class Peripheral extends BluetoothGattCallback {
939
962
  return;
940
963
  }
941
964
 
942
- // Check if we still have a valid gatt object
943
- if (gatt == null) {
944
- Log.d(BleManager.LOG_TAG, "Error, gatt is null. Fill all callbacks with an error");
945
- errorAndClearAllCallbacks("Gatt is null");
946
- resetQueuesAndBuffers();
947
- return;
948
- }
949
-
950
965
  // Execute the next command in the queue
951
966
  commandQueueBusy = true;
952
967
  mainHandler.post(new Runnable() {
@@ -1019,7 +1034,12 @@ public class Peripheral extends BluetoothGattCallback {
1019
1034
  return;
1020
1035
  } else {
1021
1036
  this.retrieveServicesCallbacks.addLast(callback);
1022
- gatt.discoverServices();
1037
+ boolean started = gatt.discoverServices();
1038
+ if (!started) {
1039
+ this.retrieveServicesCallbacks.removeLastOccurrence(callback);
1040
+ callback.invoke("Failed to start service discovery", null);
1041
+ completedCommand();
1042
+ }
1023
1043
  }
1024
1044
  });
1025
1045
  }
@@ -1029,7 +1049,7 @@ public class Peripheral extends BluetoothGattCallback {
1029
1049
  // and UUID of all characteristics instead of using
1030
1050
  // service.getCharacteristic(characteristicUUID)
1031
1051
  private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattService service,
1032
- UUID characteristicUUID) {
1052
+ UUID characteristicUUID) {
1033
1053
 
1034
1054
  if (service != null) {
1035
1055
  int read = BluetoothGattCharacteristic.PROPERTY_READ;
@@ -1051,7 +1071,7 @@ public class Peripheral extends BluetoothGattCallback {
1051
1071
  }
1052
1072
 
1053
1073
  private BluetoothGattCharacteristic findCharacteristic(BluetoothGattService service,
1054
- UUID characteristicUUID) {
1074
+ UUID characteristicUUID) {
1055
1075
 
1056
1076
  if (service != null) {
1057
1077
  return service.getCharacteristic(characteristicUUID);
@@ -1060,30 +1080,39 @@ public class Peripheral extends BluetoothGattCallback {
1060
1080
  return null;
1061
1081
  }
1062
1082
 
1063
- public boolean doWrite(final BluetoothGattCharacteristic characteristic, byte[] data, final Callback callback) {
1083
+ private void doWrite(final BluetoothGattCharacteristic characteristic, final byte[] data) {
1084
+ characteristic.setValue(data);
1085
+ if (!gatt.writeCharacteristic(characteristic)) {
1086
+ // write without response, caller will handle the callback
1087
+ for (Callback writeCallback : writeCallbacks) {
1088
+ writeCallback.invoke("Write failed", null);
1089
+ }
1090
+ writeCallbacks.clear();
1091
+ completedCommand();
1092
+ }
1093
+ }
1094
+
1095
+ private boolean enqueueWrite(final BluetoothGattCharacteristic characteristic, byte[] data, final Callback callback) {
1064
1096
  final byte[] copyOfData = copyOf(data);
1065
- return enqueue(new Runnable() {
1066
- @Override
1067
- public void run() {
1068
- characteristic.setValue(copyOfData);
1069
- if (characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
1070
- && callback != null) {
1071
- writeCallbacks.addLast(callback);
1072
- }
1073
- if (!gatt.writeCharacteristic(characteristic)) {
1074
- // write without response, caller will handle the callback
1075
- for (Callback writeCallback : writeCallbacks) {
1076
- writeCallback.invoke("Write failed", null);
1077
- }
1078
- writeCallbacks.clear();
1079
- completedCommand();
1097
+ final boolean withResponse = characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1098
+ if (withResponse && callback != null) {
1099
+ writeCallbacks.addLast(callback);
1100
+ }
1101
+ return enqueue(() -> {
1102
+ if (!isConnected() || gatt == null) {
1103
+ if (withResponse && callback != null) {
1104
+ writeCallbacks.removeLastOccurrence(callback);
1105
+ callback.invoke("Device is not connected", null);
1080
1106
  }
1107
+ completedCommand();
1108
+ return;
1081
1109
  }
1110
+ doWrite(characteristic, copyOfData);
1082
1111
  });
1083
1112
  }
1084
1113
 
1085
1114
  public void write(UUID serviceUUID, UUID characteristicUUID, byte[] data, Integer maxByteSize,
1086
- Integer queueSleepTime, Callback callback, int writeType) {
1115
+ Integer queueSleepTime, Callback callback, int writeType) {
1087
1116
  enqueue(() -> {
1088
1117
  if (!isConnected() || gatt == null) {
1089
1118
  callback.invoke("Device is not connected", null);
@@ -1104,12 +1133,15 @@ public class Peripheral extends BluetoothGattCallback {
1104
1133
  characteristic.setWriteType(writeType);
1105
1134
 
1106
1135
  if (data.length <= maxByteSize) {
1107
- if (!doWrite(characteristic, data, callback)) {
1108
- callback.invoke("Write failed");
1109
- } else {
1110
- if (BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE == writeType) {
1111
- callback.invoke();
1136
+ if (!enqueueWrite(characteristic, data, callback)) {
1137
+ if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
1138
+ writeCallbacks.removeLastOccurrence(callback);
1112
1139
  }
1140
+ callback.invoke("Write failed");
1141
+ completedCommand();
1142
+ return;
1143
+ } else if (BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE == writeType) {
1144
+ callback.invoke();
1113
1145
  }
1114
1146
  } else {
1115
1147
  int dataLength = data.length;
@@ -1134,21 +1166,24 @@ public class Peripheral extends BluetoothGattCallback {
1134
1166
 
1135
1167
  if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
1136
1168
  writeQueue.addAll(splittedMessage);
1137
- if (!doWrite(characteristic, firstMessage, callback)) {
1169
+ if (!enqueueWrite(characteristic, firstMessage, callback)) {
1138
1170
  writeQueue.clear();
1171
+ writeCallbacks.remove(callback);
1139
1172
  callback.invoke("Write failed");
1173
+ completedCommand();
1174
+ return;
1140
1175
  }
1141
1176
  } else {
1142
1177
  try {
1143
1178
  boolean writeError = false;
1144
- if (!doWrite(characteristic, firstMessage, callback)) {
1179
+ if (!enqueueWrite(characteristic, firstMessage, callback)) {
1145
1180
  writeError = true;
1146
1181
  callback.invoke("Write failed");
1147
1182
  }
1148
1183
  if (!writeError) {
1149
1184
  Thread.sleep(queueSleepTime);
1150
1185
  for (byte[] message : splittedMessage) {
1151
- if (!doWrite(characteristic, message, callback)) {
1186
+ if (!enqueueWrite(characteristic, message, callback)) {
1152
1187
  writeError = true;
1153
1188
  callback.invoke("Write failed");
1154
1189
  break;
@@ -1159,8 +1194,14 @@ public class Peripheral extends BluetoothGattCallback {
1159
1194
  callback.invoke();
1160
1195
  }
1161
1196
  }
1197
+ if (writeError) {
1198
+ completedCommand();
1199
+ return;
1200
+ }
1162
1201
  } catch (InterruptedException e) {
1163
1202
  callback.invoke("Error during writing");
1203
+ completedCommand();
1204
+ return;
1164
1205
  }
1165
1206
  }
1166
1207
  }
@@ -1234,7 +1275,7 @@ public class Peripheral extends BluetoothGattCallback {
1234
1275
  // and UUID of all characteristics instead of using
1235
1276
  // service.getCharacteristic(characteristicUUID)
1236
1277
  private BluetoothGattCharacteristic findWritableCharacteristic(BluetoothGattService service,
1237
- UUID characteristicUUID, int writeType) {
1278
+ UUID characteristicUUID, int writeType) {
1238
1279
  try {
1239
1280
  // get write property
1240
1281
  int writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE;
@@ -1242,9 +1283,9 @@ public class Peripheral extends BluetoothGattCallback {
1242
1283
  writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
1243
1284
  }
1244
1285
 
1245
- if (service == null) {
1246
- throw new Exception("Service is null.");
1247
- }
1286
+ if (service == null) {
1287
+ throw new Exception("Service is null.");
1288
+ }
1248
1289
  List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
1249
1290
  for (BluetoothGattCharacteristic characteristic : characteristics) {
1250
1291
  if ((characteristic.getProperties() & writeProperty) != 0
@@ -36,7 +36,7 @@ public abstract class ScanManager {
36
36
 
37
37
  public abstract void stopScan(Callback callback);
38
38
 
39
- public abstract void scan(ReadableArray serviceUUIDs, final int scanSeconds, ReadableMap options, Callback callback);
39
+ public abstract void scan(ReadableMap options, Callback callback);
40
40
 
41
41
  public abstract boolean isScanning();
42
42