From 53447f3fb9500e5fbc081b73bafc807ef0fe4c50 Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 23 Mar 2026 07:18:00 +0100 Subject: [PATCH] fix: Stop retrying auto-connect after SecurityException BluetoothHeadset.connect() requires MODIFY_PHONE_STATE on modern Android, which is a system-only permission. Detect the SecurityException and stop further attempts instead of retrying every second. --- .../darken/capod/common/bluetooth/BluetoothManager2.kt | 9 +++++++++ .../capod/reaction/core/autoconnect/AutoConnect.kt | 6 ++++++ app/src/main/res/values/strings.xml | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt index 420fcdd7..06b11d28 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt @@ -304,6 +304,9 @@ class BluetoothManager2 @Inject constructor( emit(wrappedDevices) } + private var _isNudgeAvailable: Boolean = true + val isNudgeAvailable: Boolean get() = _isNudgeAvailable + suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = getBluetoothProfile().map { bluetoothProfile -> try { log(TAG) { "Nudging Android connection to $device" } @@ -317,6 +320,12 @@ class BluetoothManager2 @Inject constructor( log(TAG) { "Nudged connection to $device" } true } catch (e: Exception) { + val isSecurityException = e is SecurityException || + (e is java.lang.reflect.InvocationTargetException && e.cause is SecurityException) + if (isSecurityException) { + log(TAG, ERROR) { "nudgeConnection is permanently unavailable: missing MODIFY_PHONE_STATE permission" } + _isNudgeAvailable = false + } Bugs.report(tag = TAG, "BluetoothHeadset.connect(device) is unavailable", exception = e) false } diff --git a/app/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt b/app/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt index fdf455b6..501d46b7 100644 --- a/app/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt +++ b/app/src/main/java/eu/darken/capod/reaction/core/autoconnect/AutoConnect.kt @@ -99,6 +99,12 @@ class AutoConnect @Inject constructor( log(TAG) { "Auto connect condition ($condition) is not fullfilled: ${decision.reason}" } return@map } + + if (!bluetoothManager.isNudgeAvailable) { + log(TAG, WARN) { "nudgeConnection is not available on this device, skipping" } + return@map + } + val result = bluetoothManager.nudgeConnection(bondedDevice) log(TAG) { "nudgeConnection($bondedDevice) returned $result" } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6256fe24..f885d022 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,7 +61,7 @@ Minimum signal quality The minimum signal quality that a device needs to have to be considered yours. Auto connect - If Android does not automatically connect, we can ask it too. This will set the monitor mode setting to \'Always\'. + If Android does not automatically connect, we can ask it too. This will set the monitor mode setting to \'Always\'. May not work on newer Android versions. Auto connect condition When should we try to connect to your device? Devices