From 267b02de4008c667a99780cba98bd5078c69fad5 Mon Sep 17 00:00:00 2001
From: Omer C <639682+omercnet@users.noreply.github.com>
Date: Sat, 19 Oct 2024 20:50:45 +0300
Subject: [PATCH 1/2] feat(android): use pending intent in background
---
README.md | 1 +
android/src/main/AndroidManifest.xml | 10 ++++
.../plugins/bluetoothle/BleScanReceiver.kt | 37 +++++++++++++++
.../plugins/bluetoothle/BluetoothLe.kt | 5 +-
.../plugins/bluetoothle/DeviceScanner.kt | 47 +++++++++++++++++--
src/definitions.ts | 6 +++
6 files changed, 102 insertions(+), 4 deletions(-)
create mode 100644 android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
diff --git a/README.md b/README.md
index db1969e9..44ec05e2 100644
--- a/README.md
+++ b/README.md
@@ -918,6 +918,7 @@ Stop listening to the changes of the value of a characteristic. For an example,
| **`optionalServices`** | string[]
| For **web**, all services that will be used have to be listed under services or optionalServices, e.g. [numberToUUID(0x180f)] (see [UUID format](#uuid-format)) |
| **`allowDuplicates`** | boolean
| Normally scans will discard the second and subsequent advertisements from a single device. If you need to receive them, set allowDuplicates to true (only applicable in `requestLEScan`). (default: false) |
| **`scanMode`** | ScanMode
| Android scan mode (default: ScanMode.SCAN_MODE_BALANCED) |
+| **`usePendingIntent`** | boolean
| Use pending intent for scan results for background scanning. (Android only) https://developer.android.com/develop/connectivity/bluetooth/ble/background#find-device |
#### ScanResult
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index e79c040d..020fd2f3 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -19,5 +19,15 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
new file mode 100644
index 00000000..fbb19b2f
--- /dev/null
+++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
@@ -0,0 +1,37 @@
+package com.capacitorjs.community.plugins.bluetoothle
+
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+class BleScanReceiver : BroadcastReceiver() {
+ companion object {
+ var scanCallback: ScanCallback? = null // Reference to the existing ScanCallback
+ var action = "com.capacitorjs.community.plugins.bluetoothle.ACTION_FOUND"
+ private val TAG = BleScanReceiver::class.java.simpleName
+ }
+
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val results: List? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ // For Android 13 and above
+ intent?.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, ScanResult::class.java)
+ } else {
+ // For Android 12 and below
+ @Suppress("DEPRECATION")
+ intent?.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT)
+ }
+
+ results?.forEach { result ->
+ // Forward the results to the ScanCallback
+ scanCallback?.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result)
+ }
+ }
+}
diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt
index a0fb4734..1616a541 100644
--- a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt
+++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt
@@ -310,6 +310,7 @@ class BluetoothLe : Plugin() {
val scanFilters = getScanFilters(call) ?: return
val scanSettings = getScanSettings(call) ?: return
val namePrefix = call.getString("namePrefix", "") as String
+ val usePendingIntent = call.getBoolean("usePendingIntent", false ) as Boolean
try {
deviceScanner?.stopScanning()
@@ -327,7 +328,7 @@ class BluetoothLe : Plugin() {
showDialog = true,
)
deviceScanner?.startScanning(
- scanFilters, scanSettings, false, namePrefix, { scanResponse ->
+ scanFilters, scanSettings, false, namePrefix, usePendingIntent, { scanResponse ->
run {
if (scanResponse.success) {
if (scanResponse.device == null) {
@@ -352,6 +353,7 @@ class BluetoothLe : Plugin() {
val scanSettings = getScanSettings(call) ?: return
val namePrefix = call.getString("namePrefix", "") as String
val allowDuplicates = call.getBoolean("allowDuplicates", false) as Boolean
+ val usePendingIntent = call.getBoolean("usePendingIntent", false) as Boolean // Get the flag from JS
try {
deviceScanner?.stopScanning()
@@ -372,6 +374,7 @@ class BluetoothLe : Plugin() {
scanSettings,
allowDuplicates,
namePrefix,
+ usePendingIntent,
{ scanResponse ->
run {
if (scanResponse.success) {
diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
index d3b39794..68f1ffad 100644
--- a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
+++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
@@ -2,6 +2,7 @@ package com.capacitorjs.community.plugins.bluetoothle
import android.annotation.SuppressLint
import android.app.AlertDialog
+import android.app.PendingIntent
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanCallback
@@ -9,6 +10,8 @@ import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
+import android.content.Intent
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.widget.ArrayAdapter
@@ -52,6 +55,7 @@ class DeviceScanner(
private var stopScanHandler: Handler? = null
private var allowDuplicates: Boolean = false
private var namePrefix: String = ""
+ private var usePendingIntent: Boolean = false
private val scanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
@@ -82,6 +86,7 @@ class DeviceScanner(
scanSettings: ScanSettings,
allowDuplicates: Boolean,
namePrefix: String,
+ usePendingIntent: Boolean, // Flag to control PendingIntent vs ScanCallback
callback: (ScanResponse) -> Unit,
scanResultCallback: ((ScanResult) -> Unit)?
) {
@@ -89,14 +94,36 @@ class DeviceScanner(
this.scanResultCallback = scanResultCallback
this.allowDuplicates = allowDuplicates
this.namePrefix = namePrefix
+ this.usePendingIntent = usePendingIntent
deviceStrings.clear()
deviceList.clear()
+
if (!isScanning) {
setTimeoutForStopScanning()
- Logger.debug(TAG, "Start scanning.")
+ Logger.debug(TAG, "Start scanning" + (if (usePendingIntent) " with pendingIntent!" else "."))
isScanning = true
- bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && usePendingIntent) {
+ // Use PendingIntent for background scanning
+ val intent = Intent(context, BleScanReceiver::class.java).setAction(BleScanReceiver.action)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+
+ // Store the ScanCallback inside the BroadcastReceiver
+ BleScanReceiver.scanCallback = scanCallback
+
+ // Start the scan with PendingIntent
+ bluetoothLeScanner?.startScan(scanFilters, scanSettings, pendingIntent)
+ } else {
+ // Use ScanCallback for foreground scanning
+ bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
+ }
+
if (showDialog) {
dialogHandler = Handler(Looper.getMainLooper())
showDeviceList()
@@ -133,7 +160,21 @@ class DeviceScanner(
}
Logger.debug(TAG, "Stop scanning.")
isScanning = false
- bluetoothLeScanner?.stopScan(scanCallback)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && usePendingIntent) {
+ // Stop scan using PendingIntent
+ val intent = Intent(context, BleScanReceiver::class.java).setAction(BleScanReceiver.action)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ bluetoothLeScanner?.stopScan(pendingIntent)
+ } else {
+ // Stop scan using ScanCallback
+ bluetoothLeScanner?.stopScan(scanCallback)
+ }
}
private fun showDeviceList() {
diff --git a/src/definitions.ts b/src/definitions.ts
index ce980a21..5e494da0 100644
--- a/src/definitions.ts
+++ b/src/definitions.ts
@@ -46,6 +46,12 @@ export interface RequestBleDeviceOptions {
* Android scan mode (default: ScanMode.SCAN_MODE_BALANCED)
*/
scanMode?: ScanMode;
+
+ /**
+ * Use pending intent for scan results for background scanning. (Android only)
+ * https://developer.android.com/develop/connectivity/bluetooth/ble/background#find-device
+ */
+ usePendingIntent?: boolean;
}
/**
From bb95dec54e16eb39d358abe75c0b7a0d234bcb9e Mon Sep 17 00:00:00 2001
From: Omer C <639682+omercnet@users.noreply.github.com>
Date: Sat, 26 Oct 2024 12:49:39 +0300
Subject: [PATCH 2/2] no cation, fix manifest
---
android/src/main/AndroidManifest.xml | 39 +++++++++++--------
.../plugins/bluetoothle/BleScanReceiver.kt | 28 +++++++------
.../plugins/bluetoothle/DeviceScanner.kt | 7 ++--
3 files changed, 42 insertions(+), 32 deletions(-)
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 020fd2f3..d73d5df5 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -3,31 +3,38 @@
+
+
+
+
+
+
-
+ android:usesPermissionFlags="neverForLocation" />
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
index fbb19b2f..44b1c46b 100644
--- a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
+++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BleScanReceiver.kt
@@ -10,28 +10,32 @@ import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
+@RequiresApi(Build.VERSION_CODES.O)
class BleScanReceiver : BroadcastReceiver() {
companion object {
var scanCallback: ScanCallback? = null // Reference to the existing ScanCallback
- var action = "com.capacitorjs.community.plugins.bluetoothle.ACTION_FOUND"
private val TAG = BleScanReceiver::class.java.simpleName
}
- @RequiresApi(Build.VERSION_CODES.O)
- override fun onReceive(context: Context?, intent: Intent?) {
- val results: List? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- // For Android 13 and above
- intent?.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, ScanResult::class.java)
- } else {
- // For Android 12 and below
- @Suppress("DEPRECATION")
- intent?.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT)
- }
+ override fun onReceive(context: Context, intent: Intent) {
+ val results = intent.getScanResults()
+
- results?.forEach { result ->
+ results.forEach { result ->
// Forward the results to the ScanCallback
scanCallback?.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result)
}
}
+
+ private fun Intent.getScanResults(): List =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getParcelableArrayListExtra(
+ BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT,
+ ScanResult::class.java,
+ )
+ } else {
+ @Suppress("DEPRECATION")
+ getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT)
+ } ?: emptyList()
}
diff --git a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
index 68f1ffad..c736a109 100644
--- a/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
+++ b/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
@@ -106,11 +106,10 @@ class DeviceScanner(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && usePendingIntent) {
// Use PendingIntent for background scanning
- val intent = Intent(context, BleScanReceiver::class.java).setAction(BleScanReceiver.action)
val pendingIntent = PendingIntent.getBroadcast(
context,
- 0,
- intent,
+ 1,
+ Intent(context, BleScanReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
@@ -163,7 +162,7 @@ class DeviceScanner(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && usePendingIntent) {
// Stop scan using PendingIntent
- val intent = Intent(context, BleScanReceiver::class.java).setAction(BleScanReceiver.action)
+ val intent = Intent(context, BleScanReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,