react-native-ble-manager 12.2.2 → 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,22 +1,23 @@
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
 
17
18
  import java.nio.ByteBuffer;
18
19
  import java.util.Map;
19
-
20
+ import java.util.Objects;
20
21
 
21
22
  @SuppressLint("MissingPermission")
22
23
  public class DefaultPeripheral extends Peripheral {
@@ -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,13 +41,17 @@ public class DefaultPeripheral extends Peripheral {
40
41
  WritableMap advertising = Arguments.createMap();
41
42
 
42
43
  try {
43
- map.putString("name", device.getName());
44
+ String name = getSafeDeviceName();
45
+ if (name == null && scanResult != null && scanResult.getScanRecord() != null) {
46
+ name = scanResult.getScanRecord().getDeviceName();
47
+ }
48
+ map.putString("name", name);
44
49
  map.putString("id", device.getAddress()); // mac address
45
50
  map.putInt("rssi", advertisingRSSI);
46
51
 
47
52
  advertising.putMap("rawData", byteArrayToWritableMap(advertisingDataBytes));
48
53
 
49
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
54
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
50
55
  // We can check if peripheral is connectable using the scanresult
51
56
  if (this.scanResult != null) {
52
57
  advertising.putBoolean("isConnectable", scanResult.isConnectable());
@@ -62,7 +67,7 @@ public class DefaultPeripheral extends Peripheral {
62
67
  advertising.putString("localName", deviceName.replace("\0", ""));
63
68
 
64
69
  WritableArray serviceUuids = Arguments.createArray();
65
- if (advertisingData.getServiceUuids() != null && advertisingData.getServiceUuids().size() != 0) {
70
+ if (advertisingData.getServiceUuids() != null && !advertisingData.getServiceUuids().isEmpty()) {
66
71
  for (ParcelUuid uuid : advertisingData.getServiceUuids()) {
67
72
  serviceUuids.pushString(UUIDHelper.uuidToString(uuid.getUuid()));
68
73
  }
@@ -102,7 +107,7 @@ public class DefaultPeripheral extends Peripheral {
102
107
 
103
108
  map.putMap("advertising", advertising);
104
109
  } catch (Exception e) { // this shouldn't happen
105
- e.printStackTrace();
110
+ Log.e(LOG_TAG, "asWritableMap error", e);
106
111
  }
107
112
 
108
113
  return map;
@@ -111,6 +116,6 @@ public class DefaultPeripheral extends Peripheral {
111
116
  public void updateData(ScanResult result) {
112
117
  scanResult = result;
113
118
  advertisingData = result.getScanRecord();
114
- advertisingDataBytes = advertisingData.getBytes();
119
+ advertisingDataBytes = advertisingData != null ? advertisingData.getBytes() : null;
115
120
  }
116
121
  }
@@ -7,19 +7,24 @@ import android.Manifest;
7
7
  import android.annotation.SuppressLint;
8
8
  import android.bluetooth.BluetoothAdapter;
9
9
  import android.bluetooth.BluetoothDevice;
10
- import android.bluetooth.BluetoothDevice;
11
10
  import android.bluetooth.le.BluetoothLeScanner;
12
11
  import android.bluetooth.le.ScanCallback;
13
12
  import android.bluetooth.le.ScanFilter;
14
13
  import android.bluetooth.le.ScanRecord;
15
14
  import android.bluetooth.le.ScanResult;
16
15
  import android.bluetooth.le.ScanSettings;
16
+ import android.app.PendingIntent;
17
+ import android.content.BroadcastReceiver;
18
+ import android.content.Context;
19
+ import android.content.Intent;
20
+ import android.content.IntentFilter;
17
21
  import android.content.pm.PackageManager;
18
22
  import android.os.Build;
19
23
  import android.os.ParcelUuid;
20
24
  import android.util.Log;
21
25
 
22
26
  import androidx.core.app.ActivityCompat;
27
+ import androidx.core.content.ContextCompat;
23
28
 
24
29
  import com.facebook.react.bridge.Arguments;
25
30
  import com.facebook.react.bridge.Callback;
@@ -31,11 +36,20 @@ import com.facebook.react.bridge.WritableMap;
31
36
  import java.util.ArrayList;
32
37
  import java.util.Arrays;
33
38
  import java.util.List;
39
+ import java.util.Objects;
34
40
 
35
41
  @SuppressLint("MissingPermission")
36
42
  public class DefaultScanManager extends ScanManager {
37
43
 
38
44
  private boolean isScanning = false;
45
+ private PendingIntent scanPendingIntent;
46
+ private BroadcastReceiver scanReceiver;
47
+ private boolean scanReceiverRegistered = false;
48
+ private boolean scanningWithIntent = false;
49
+ private static final String ACTION_SCAN_RESULT = "it.innove.BleManager.ACTION_SCAN_RESULT";
50
+ private static final String EXTRA_LIST_SCAN_RESULT = "android.bluetooth.le.extra.LIST_SCAN_RESULT";
51
+ private static final String EXTRA_SCAN_RESULT = "android.bluetooth.le.extra.SCAN_RESULT";
52
+ private static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
39
53
 
40
54
  public DefaultScanManager(ReactApplicationContext reactContext, BleManager bleManager) {
41
55
  super(reactContext, bleManager);
@@ -47,15 +61,12 @@ public class DefaultScanManager extends ScanManager {
47
61
  // update scanSessionId to prevent stopping next scan by running timeout thread
48
62
  scanSessionId.incrementAndGet();
49
63
 
50
- final BluetoothLeScanner scanner = getBluetoothAdapter().getBluetoothLeScanner();
51
- if (scanner != null)
52
- scanner.stopScan(mScanCallback);
53
- isScanning = false;
64
+ stopActiveScan();
54
65
  callback.invoke();
55
66
  }
56
67
 
57
68
  @Override
58
- public void scan(ReadableArray serviceUUIDs, final int scanSeconds, ReadableMap options, Callback callback) {
69
+ public void scan(ReadableMap options, Callback callback) {
59
70
  ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
60
71
  List<ScanFilter> filters = new ArrayList<>();
61
72
 
@@ -67,16 +78,14 @@ public class DefaultScanManager extends ScanManager {
67
78
  scanSettingsBuilder.setScanMode(options.getInt("scanMode"));
68
79
  }
69
80
 
70
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
71
- if (options.hasKey("numberOfMatches")) {
72
- scanSettingsBuilder.setNumOfMatches(options.getInt("numberOfMatches"));
73
- }
74
- if (options.hasKey("matchMode")) {
75
- scanSettingsBuilder.setMatchMode(options.getInt("matchMode"));
76
- }
77
- if (options.hasKey("callbackType")) {
78
- scanSettingsBuilder.setCallbackType(options.getInt("callbackType"));
79
- }
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"));
80
89
  }
81
90
 
82
91
  if (options.hasKey("reportDelay")) {
@@ -93,9 +102,10 @@ public class DefaultScanManager extends ScanManager {
93
102
  }
94
103
  }
95
104
 
96
- if (serviceUUIDs.size() > 0) {
105
+ ReadableArray serviceUUIDs = options.getArray("serviceUUIDs");
106
+ if (serviceUUIDs != null && serviceUUIDs.size() > 0) {
97
107
  for (int i = 0; i < serviceUUIDs.size(); i++) {
98
- 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();
99
109
  filters.add(filter);
100
110
  Log.d(BleManager.LOG_TAG, "Filter service: " + serviceUUIDs.getString(i));
101
111
  }
@@ -103,11 +113,17 @@ public class DefaultScanManager extends ScanManager {
103
113
 
104
114
 
105
115
  if (options.hasKey("exactAdvertisingName")) {
106
- ArrayList<Object> expectedNames = options.getArray("exactAdvertisingName").toArrayList();
107
- Log.d(BleManager.LOG_TAG, "Filter on advertising names:" + expectedNames);
108
- for (Object name : expectedNames) {
109
- ScanFilter filter = new ScanFilter.Builder().setDeviceName(name.toString()).build();
110
- filters.add(filter);
116
+ ReadableArray exactAdvertisingNameArray = options.getArray("exactAdvertisingName");
117
+ if (exactAdvertisingNameArray == null) {
118
+ Log.w(BleManager.LOG_TAG, "exactAdvertisingName key present but array is null");
119
+ }
120
+ if (exactAdvertisingNameArray != null) {
121
+ ArrayList<Object> expectedNames = exactAdvertisingNameArray.toArrayList();
122
+ Log.d(BleManager.LOG_TAG, "Filter on advertising names:" + expectedNames);
123
+ for (Object name : expectedNames) {
124
+ ScanFilter filter = new ScanFilter.Builder().setDeviceName(name.toString()).build();
125
+ filters.add(filter);
126
+ }
111
127
  }
112
128
  }
113
129
 
@@ -154,9 +170,58 @@ public class DefaultScanManager extends ScanManager {
154
170
  }
155
171
  }
156
172
 
157
- getBluetoothAdapter().getBluetoothLeScanner().startScan(filters, scanSettingsBuilder.build(), mScanCallback);
173
+ boolean useScanIntent = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
174
+ && options.hasKey("useScanIntent")
175
+ && options.getBoolean("useScanIntent");
176
+
177
+ if (useScanIntent && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
178
+ callback.invoke("useScanIntent requires Android O (API 26) or higher");
179
+ return;
180
+ }
181
+
182
+ if (isScanning) {
183
+ scanSessionId.incrementAndGet();
184
+ stopActiveScan();
185
+ }
186
+
187
+ BluetoothLeScanner scanner = getBluetoothAdapter().getBluetoothLeScanner();
188
+ if (scanner == null) {
189
+ callback.invoke("No BLE scanner available");
190
+ return;
191
+ }
192
+
193
+ try {
194
+ if (useScanIntent) {
195
+ Log.i(BleManager.LOG_TAG, "Scan with intent");
196
+ ensureScanReceiver();
197
+ Intent intent = new Intent(ACTION_SCAN_RESULT);
198
+ intent.setPackage(context.getPackageName());
199
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
200
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
201
+ flags |= PendingIntent.FLAG_MUTABLE;
202
+ }
203
+ scanPendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags);
204
+ scanner.startScan(filters, scanSettingsBuilder.build(), scanPendingIntent);
205
+ scanningWithIntent = true;
206
+ } else {
207
+ scanner.startScan(filters, scanSettingsBuilder.build(), mScanCallback);
208
+ scanningWithIntent = false;
209
+ }
210
+ } catch (Exception e) {
211
+ if (useScanIntent) {
212
+ if (scanPendingIntent != null) {
213
+ scanPendingIntent.cancel();
214
+ scanPendingIntent = null;
215
+ }
216
+ unregisterScanReceiver();
217
+ }
218
+ callback.invoke("Failed to start scan: " + e.getMessage());
219
+ return;
220
+ }
221
+
158
222
  isScanning = true;
159
223
 
224
+ long scanSeconds = (long) options.getDouble("seconds");
160
225
  if (scanSeconds > 0) {
161
226
  Thread thread = new Thread() {
162
227
  private final int currentScanSession = scanSessionId.incrementAndGet();
@@ -165,7 +230,7 @@ public class DefaultScanManager extends ScanManager {
165
230
  public void run() {
166
231
 
167
232
  try {
168
- Thread.sleep(scanSeconds * 1000);
233
+ Thread.sleep(scanSeconds * 1000L);
169
234
  } catch (InterruptedException ignored) {
170
235
  }
171
236
 
@@ -177,9 +242,7 @@ public class DefaultScanManager extends ScanManager {
177
242
  // check current scan session was not stopped
178
243
  if (scanSessionId.intValue() == currentScanSession) {
179
244
  if (btAdapter.getState() == BluetoothAdapter.STATE_ON) {
180
- final BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
181
- if (scanner != null) scanner.stopScan(mScanCallback);
182
- isScanning = false;
245
+ stopActiveScan();
183
246
  }
184
247
 
185
248
  WritableMap map = Arguments.createMap();
@@ -269,4 +332,87 @@ public class DefaultScanManager extends ScanManager {
269
332
  public void setScanning(boolean scanning) {
270
333
  isScanning = scanning;
271
334
  }
335
+
336
+ private void stopActiveScan() {
337
+ BluetoothLeScanner scanner = getBluetoothAdapter() != null ? getBluetoothAdapter().getBluetoothLeScanner() : null;
338
+ if (scanner != null) {
339
+ try {
340
+ if (scanningWithIntent && scanPendingIntent != null) {
341
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
342
+ scanner.stopScan(scanPendingIntent);
343
+ }
344
+ } else {
345
+ scanner.stopScan(mScanCallback);
346
+ }
347
+ } catch (IllegalArgumentException | IllegalStateException ignored) {
348
+ Log.w(BleManager.LOG_TAG, "stopScan ignored error: " + ignored.getMessage());
349
+ }
350
+ }
351
+ if (scanPendingIntent != null) {
352
+ scanPendingIntent.cancel();
353
+ scanPendingIntent = null;
354
+ }
355
+ unregisterScanReceiver();
356
+ scanningWithIntent = false;
357
+ isScanning = false;
358
+ }
359
+
360
+ private void ensureScanReceiver() {
361
+ if (scanReceiver == null) {
362
+ scanReceiver = new BroadcastReceiver() {
363
+ @Override
364
+ public void onReceive(Context context, Intent intent) {
365
+ if (!ACTION_SCAN_RESULT.equals(intent.getAction())) {
366
+ return;
367
+ }
368
+
369
+ if (intent.hasExtra(EXTRA_ERROR_CODE)) {
370
+ final int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
371
+ runOnUiThread(new Runnable() {
372
+ @Override
373
+ public void run() {
374
+ stopActiveScan();
375
+ WritableMap map = Arguments.createMap();
376
+ map.putInt("status", errorCode);
377
+ bleManager.emitOnStopScan(map);
378
+ }
379
+ });
380
+ return;
381
+ }
382
+
383
+ final ArrayList<ScanResult> results = intent.getParcelableArrayListExtra(EXTRA_LIST_SCAN_RESULT);
384
+ final ScanResult singleResult = intent.getParcelableExtra(EXTRA_SCAN_RESULT);
385
+
386
+ runOnUiThread(new Runnable() {
387
+ @Override
388
+ public void run() {
389
+ if (results != null) {
390
+ for (ScanResult result : results) {
391
+ onDiscoveredPeripheral(result);
392
+ }
393
+ } else if (singleResult != null) {
394
+ onDiscoveredPeripheral(singleResult);
395
+ }
396
+ }
397
+ });
398
+ }
399
+ };
400
+ }
401
+
402
+ if (!scanReceiverRegistered) {
403
+ IntentFilter intentFilter = new IntentFilter(ACTION_SCAN_RESULT);
404
+ ContextCompat.registerReceiver(context, scanReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
405
+ scanReceiverRegistered = true;
406
+ }
407
+ }
408
+
409
+ private void unregisterScanReceiver() {
410
+ if (scanReceiverRegistered) {
411
+ try {
412
+ context.unregisterReceiver(scanReceiver);
413
+ } catch (IllegalArgumentException ignored) {
414
+ }
415
+ scanReceiverRegistered = false;
416
+ }
417
+ }
272
418
  }