Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): use pending intent in background #700

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ Stop listening to the changes of the value of a characteristic. For an example,
| **`optionalServices`** | <code>string[]</code> | 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`** | <code>boolean</code> | 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`** | <code><a href="#scanmode">ScanMode</a></code> | Android scan mode (default: <a href="#scanmode">ScanMode.SCAN_MODE_BALANCED</a>) |
| **`usePendingIntent`** | <code>boolean</code> | Use pending intent for scan results for background scanning. (Android only) https://developer.android.com/develop/connectivity/bluetooth/ble/background#find-device |

#### ScanResult

Expand Down
35 changes: 26 additions & 9 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,38 @@
<!-- Bluetooth -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These permission changes do not seem necessary. Are you able to revert everything other than the new receiver block?

<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can strongly assert that your app
doesn't derive physical location. -->
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
tools:targetApi="s" />
<uses-permission
android:name="android.permission.BLUETOOTH_CONNECT"
tools:targetApi="s" />
android:usesPermissionFlags="neverForLocation" />

<!-- Needed only if your app communicates with already-paired Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- Needed only if your app uses Bluetooth scan results to derive physical location otherwise
it's not needed for device running Android 12 or above -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application>

<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
</manifest>
<receiver
android:name=".BleScanReceiver"
android:exported="false" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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

@RequiresApi(Build.VERSION_CODES.O)
class BleScanReceiver : BroadcastReceiver() {
companion object {
var scanCallback: ScanCallback? = null // Reference to the existing ScanCallback
private val TAG = BleScanReceiver::class.java.simpleName
}


override fun onReceive(context: Context, intent: Intent) {
val results = intent.getScanResults()


results.forEach { result ->
// Forward the results to the ScanCallback
scanCallback?.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result)
}
}

private fun Intent.getScanResults(): List<ScanResult> =
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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand All @@ -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()
Expand All @@ -372,6 +374,7 @@ class BluetoothLe : Plugin() {
scanSettings,
allowDuplicates,
namePrefix,
usePendingIntent,
{ scanResponse ->
run {
if (scanResponse.success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ 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
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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -82,21 +86,43 @@ class DeviceScanner(
scanSettings: ScanSettings,
allowDuplicates: Boolean,
namePrefix: String,
usePendingIntent: Boolean, // Flag to control PendingIntent vs ScanCallback
callback: (ScanResponse) -> Unit,
scanResultCallback: ((ScanResult) -> Unit)?
) {
this.savedCallback = callback
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 "."))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor, but it would be better to put this debug log into the if statement so the log is accurate for Android 7 and lower

isScanning = true
bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && usePendingIntent) {
// Use PendingIntent for background scanning
val pendingIntent = PendingIntent.getBroadcast(
context,
1,
Intent(context, BleScanReceiver::class.java),
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()
Expand Down Expand Up @@ -133,7 +159,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)
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() {
Expand Down
6 changes: 6 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
Loading