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,