mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 11:16:12 -04:00
fix(monitor): Recover from revoked Bluetooth scan permission
This commit is contained in:
@@ -16,6 +16,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.notifications.PendingIntentCompat
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -113,22 +114,7 @@ class BleScanner @Inject constructor(
|
||||
null
|
||||
}
|
||||
|
||||
val flushJob = if (!disableDirectScanCallback) {
|
||||
launch {
|
||||
log(TAG) { "Flush job launched" }
|
||||
while (isActive) {
|
||||
// Can undercut the minimum setReportDelay(), e.g. 5000ms on a Pixel5@12
|
||||
adapter.bluetoothLeScanner.flushPendingScanResults(callback)
|
||||
when (scannerMode) {
|
||||
ScannerMode.LOW_POWER -> break
|
||||
ScannerMode.BALANCED -> delay(2000)
|
||||
ScannerMode.LOW_LATENCY -> delay(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
var flushJob: Job? = null
|
||||
|
||||
val filterList = when {
|
||||
useOffloadedFiltering -> filters.toList()
|
||||
@@ -167,26 +153,55 @@ class BleScanner @Inject constructor(
|
||||
setReportDelay(delay)
|
||||
}.build()
|
||||
|
||||
if (disableDirectScanCallback) {
|
||||
val callbackIntent = createStartIntent()
|
||||
log(TAG) {
|
||||
"startScan(mode=$scannerMode, filterCount=${filterList.size}, batching=$useOffloadedBatching, filtering=$useOffloadedFiltering, callback=intent)"
|
||||
try {
|
||||
if (disableDirectScanCallback) {
|
||||
val callbackIntent = createStartIntent()
|
||||
log(TAG) {
|
||||
"startScan(mode=$scannerMode, filterCount=${filterList.size}, batching=$useOffloadedBatching, filtering=$useOffloadedFiltering, callback=intent)"
|
||||
}
|
||||
scanner.startScan(filterList, scanSettings, callbackIntent)
|
||||
} else {
|
||||
log(TAG) {
|
||||
"startScan(mode=$scannerMode, filterCount=${filterList.size}, batching=$useOffloadedBatching, filtering=$useOffloadedFiltering, callback=direct)"
|
||||
}
|
||||
scanner.startScan(filterList, scanSettings, callback)
|
||||
flushJob = launch {
|
||||
log(TAG) { "Flush job launched" }
|
||||
while (isActive) {
|
||||
try {
|
||||
// Can undercut the minimum setReportDelay(), e.g. 5000ms on a Pixel5@12
|
||||
scanner.flushPendingScanResults(callback)
|
||||
} catch (e: SecurityException) {
|
||||
log(TAG, WARN) { "flushPendingScanResults() denied: ${e.message}" }
|
||||
close(e)
|
||||
break
|
||||
}
|
||||
when (scannerMode) {
|
||||
ScannerMode.LOW_POWER -> break
|
||||
ScannerMode.BALANCED -> delay(2000)
|
||||
ScannerMode.LOW_LATENCY -> delay(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scanner.startScan(filterList, scanSettings, callbackIntent)
|
||||
} else {
|
||||
log(TAG) {
|
||||
"startScan(mode=$scannerMode, filterCount=${filterList.size}, batching=$useOffloadedBatching, filtering=$useOffloadedFiltering, callback=direct)"
|
||||
}
|
||||
scanner.startScan(filterList, scanSettings, callback)
|
||||
} catch (e: SecurityException) {
|
||||
log(TAG, WARN) { "startScan() denied: ${e.message}" }
|
||||
forwarderConsumer?.cancel()
|
||||
close(e)
|
||||
return@callbackFlow
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
forwarderConsumer?.cancel()
|
||||
flushJob?.cancel()
|
||||
if (disableDirectScanCallback) {
|
||||
scanner.stopScan(createStopIntent())
|
||||
} else {
|
||||
scanner.stopScan(callback)
|
||||
try {
|
||||
if (disableDirectScanCallback) {
|
||||
scanner.stopScan(createStopIntent())
|
||||
} else {
|
||||
scanner.stopScan(callback)
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
log(TAG, WARN) { "stopScan() denied: ${e.message}" }
|
||||
}
|
||||
log(TAG) { "BleScanner stopped" }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package eu.darken.capod.common.permissions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.PermissionChecker
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.withinApiLevel
|
||||
@@ -18,7 +17,7 @@ enum class Permission(
|
||||
val permissionId: String,
|
||||
val isScanBlocking: Boolean = false,
|
||||
val isGranted: (Context) -> Boolean = {
|
||||
ContextCompat.checkSelfPermission(it, permissionId) == PackageManager.PERMISSION_GRANTED
|
||||
PermissionChecker.checkSelfPermission(it, permissionId) == PermissionChecker.PERMISSION_GRANTED
|
||||
},
|
||||
) {
|
||||
BLUETOOTH(
|
||||
@@ -87,4 +86,4 @@ enum class Permission(
|
||||
fun Permission.isRequired(context: Context): Boolean = when {
|
||||
!withinApiLevel(minApiLevel, maxApiLevel) -> false
|
||||
else -> !isGranted(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
@@ -51,7 +53,7 @@ class BlePodMonitor @Inject constructor(
|
||||
private val generalSettings: GeneralSettings,
|
||||
bluetoothManager: BluetoothManager2,
|
||||
private val debugSettings: DebugSettings,
|
||||
permissionTool: PermissionTool,
|
||||
private val permissionTool: PermissionTool,
|
||||
private val profilesRepo: DeviceProfilesRepo,
|
||||
) {
|
||||
|
||||
@@ -89,8 +91,10 @@ class BlePodMonitor @Inject constructor(
|
||||
log(
|
||||
TAG,
|
||||
Logging.Priority.WARN
|
||||
) { "PodMonitor failed due to missing permission, not retrying: ${cause.asLog()}" }
|
||||
false
|
||||
) { "PodMonitor failed due to missing permission, rechecking and retrying: ${cause.asLog()}" }
|
||||
permissionTool.recheck()
|
||||
delay(3000)
|
||||
true
|
||||
} else {
|
||||
log(TAG, Logging.Priority.WARN) { "PodMonitor failed (attempt=$attempt), will retry: ${cause.asLog()}" }
|
||||
delay(3000)
|
||||
@@ -160,13 +164,27 @@ class BlePodMonitor @Inject constructor(
|
||||
else -> ProximityPairing.getBleScanFilter()
|
||||
}
|
||||
|
||||
bleScanner.scan(
|
||||
filters = filters,
|
||||
scannerMode = options.scannerMode,
|
||||
disableOffloadFiltering = options.offloadedFilteringDisabled,
|
||||
disableOffloadBatching = options.offloadedBatchingDisabled,
|
||||
disableDirectScanCallback = options.disableDirectCallback,
|
||||
)
|
||||
flow {
|
||||
emitAll(
|
||||
bleScanner.scan(
|
||||
filters = filters,
|
||||
scannerMode = options.scannerMode,
|
||||
disableOffloadFiltering = options.offloadedFilteringDisabled,
|
||||
disableOffloadBatching = options.offloadedBatchingDisabled,
|
||||
disableDirectScanCallback = options.disableDirectCallback,
|
||||
)
|
||||
)
|
||||
}.catch { cause ->
|
||||
if (cause is SecurityException) {
|
||||
log(TAG, Logging.Priority.WARN) {
|
||||
"BLE scanner failed due to missing permission, rechecking permissions: ${cause.asLog()}"
|
||||
}
|
||||
permissionTool.recheck()
|
||||
emit(emptyList())
|
||||
} else {
|
||||
throw cause
|
||||
}
|
||||
}
|
||||
}
|
||||
.map { it.onlyNewAndUnique() }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user