mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-07-09 21:05:24 -04:00
feat(ble): update kable-core to v0.44.0 + surface missing-permission scan failures (#6165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: James Rich <james.a.rich@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1
.skills/compose-ui/strings-index.txt
generated
1
.skills/compose-ui/strings-index.txt
generated
@@ -123,6 +123,7 @@ bluetooth_feature_config_description
|
||||
bluetooth_feature_discovery
|
||||
bluetooth_feature_discovery_description
|
||||
bluetooth_permission
|
||||
bluetooth_scan_missing_permission
|
||||
bluetooth_scan_start_failed
|
||||
bold_heading
|
||||
bonding_failed_permissions
|
||||
|
||||
@@ -22,6 +22,10 @@ enum class BleScanStartFailureReason(val androidCode: String, val description: S
|
||||
androidCode = "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED(2)",
|
||||
description = "Android could not register the app for BLE scanning",
|
||||
),
|
||||
MissingScanPermission(
|
||||
androidCode = "MISSING_SCAN_PERMISSION",
|
||||
description = "A runtime permission required for BLE scanning is not granted",
|
||||
),
|
||||
}
|
||||
|
||||
/** A discovery scan-start failure. No advertisements can be delivered until a future scan starts successfully. */
|
||||
|
||||
@@ -98,15 +98,20 @@ private fun Throwable.asBleScanStartExceptionOrNull(): BleScanStartException? {
|
||||
var current: Throwable? = this
|
||||
var depth = 0
|
||||
while (current != null && depth < MAX_SCAN_START_FAILURE_CAUSE_DEPTH) {
|
||||
if (current.isApplicationRegistrationFailure()) {
|
||||
return BleScanStartException(BleScanStartFailureReason.ApplicationRegistrationFailed, this)
|
||||
}
|
||||
val reason = current.scanStartFailureReasonOrNull()
|
||||
if (reason != null) return BleScanStartException(reason, this)
|
||||
current = current.cause
|
||||
depth++
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Throwable.scanStartFailureReasonOrNull(): BleScanStartFailureReason? = when {
|
||||
isApplicationRegistrationFailure() -> BleScanStartFailureReason.ApplicationRegistrationFailed
|
||||
isMissingScanPermission() -> BleScanStartFailureReason.MissingScanPermission
|
||||
else -> null
|
||||
}
|
||||
|
||||
// Kable exposes Android scan-start registration failure as an IllegalStateException message,
|
||||
// so keep this matcher narrow and local to the scanner adapter.
|
||||
private fun Throwable.isApplicationRegistrationFailure(): Boolean = this is IllegalStateException &&
|
||||
@@ -114,3 +119,11 @@ private fun Throwable.isApplicationRegistrationFailure(): Boolean = this is Ille
|
||||
failureMessage.contains("app cannot be registered", ignoreCase = true) ||
|
||||
failureMessage.contains("SCAN_FAILED_APPLICATION_REGISTRATION_FAILED", ignoreCase = true)
|
||||
} == true
|
||||
|
||||
// Kable's scan-permission check throws a plain IllegalStateException "Missing required <permission> for scanning"
|
||||
// (ACCESS_COARSE/FINE_LOCATION on Android <12, BLUETOOTH_SCAN on 12+). Match the stable message anchors.
|
||||
private fun Throwable.isMissingScanPermission(): Boolean = this is IllegalStateException &&
|
||||
message?.let { failureMessage ->
|
||||
failureMessage.contains("Missing required", ignoreCase = true) &&
|
||||
failureMessage.contains("for scanning", ignoreCase = true)
|
||||
} == true
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.meshtastic.core.ble
|
||||
|
||||
import com.juul.kable.Advertisement
|
||||
import com.juul.kable.ExperimentalApi
|
||||
import com.juul.kable.ExperimentalKableApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -54,7 +54,7 @@ class MeshtasticBleDevice(
|
||||
|
||||
override val rssi: Int? = advertisement?.rssi
|
||||
|
||||
@OptIn(ExperimentalApi::class)
|
||||
@OptIn(ExperimentalKableApi::class)
|
||||
override suspend fun readRssi(): Int {
|
||||
val active = ActiveBleConnection.active
|
||||
return if (active != null && active.address == address) {
|
||||
|
||||
@@ -145,6 +145,16 @@ class KableBleConnectionTest {
|
||||
assertEquals(BleScanStartFailureReason.ApplicationRegistrationFailed, failure.reason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `scan wraps missing scan permission failure`() = runTest {
|
||||
val message = "Missing required android.permission.ACCESS_COARSE_LOCATION for scanning"
|
||||
val scanner = TestKableBleScanner(scanResults = flow { throw IllegalStateException(message) })
|
||||
|
||||
val failure = assertFailsWith<BleScanStartException> { scanner.scan(timeout = 1.seconds).toList() }
|
||||
|
||||
assertEquals(BleScanStartFailureReason.MissingScanPermission, failure.reason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `scan preserves unrelated illegal state failure`() = runTest {
|
||||
val scanner =
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<string name="bluetooth_feature_discovery">Discovery</string>
|
||||
<string name="bluetooth_feature_discovery_description">Find and identify Meshtastic devices near you.</string>
|
||||
<string name="bluetooth_permission">Bluetooth</string>
|
||||
<string name="bluetooth_scan_missing_permission">Bluetooth scan needs permission. Grant the Nearby devices (or Location) permission to find devices.</string>
|
||||
<string name="bluetooth_scan_start_failed">Bluetooth scan couldn't start. Try again, or toggle Bluetooth if the problem continues.</string>
|
||||
<string name="bold_heading">Bold Heading</string>
|
||||
<string name="bonding_failed_permissions">Pairing failed. Grant nearby device permissions and try again.</string>
|
||||
|
||||
@@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.ble.BleDevice
|
||||
import org.meshtastic.core.ble.BleScanStartException
|
||||
import org.meshtastic.core.ble.BleScanStartFailureReason
|
||||
import org.meshtastic.core.ble.BleScanner
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants
|
||||
import org.meshtastic.core.common.util.safeCatchingAll
|
||||
@@ -58,6 +59,7 @@ import org.meshtastic.core.repository.RadioPrefs
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.bluetooth_scan_missing_permission
|
||||
import org.meshtastic.core.resources.bluetooth_scan_start_failed
|
||||
import org.meshtastic.core.resources.getStringSuspend
|
||||
import org.meshtastic.core.ui.viewmodel.safeLaunch
|
||||
@@ -396,9 +398,13 @@ open class ScannerViewModel(
|
||||
Logger.w(exception) {
|
||||
"BLE scan could not start: ${exception.reason.androidCode} (${exception.reason.description})"
|
||||
}
|
||||
val messageRes =
|
||||
when (exception.reason) {
|
||||
BleScanStartFailureReason.MissingScanPermission -> Res.string.bluetooth_scan_missing_permission
|
||||
BleScanStartFailureReason.ApplicationRegistrationFailed -> Res.string.bluetooth_scan_start_failed
|
||||
}
|
||||
val errorMessage =
|
||||
safeCatchingAll { getStringSuspend(Res.string.bluetooth_scan_start_failed) }
|
||||
.getOrDefault(BLE_SCAN_START_FAILURE_MESSAGE_FALLBACK)
|
||||
safeCatchingAll { getStringSuspend(messageRes) }.getOrDefault(BLE_SCAN_START_FAILURE_MESSAGE_FALLBACK)
|
||||
serviceRepository.setErrorMessage(text = errorMessage, severity = Severity.Warn)
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ uri-kmp = "0.0.21"
|
||||
osmdroid-android = "6.1.20"
|
||||
spotless = "8.8.0"
|
||||
vico = "3.3.0-next.1"
|
||||
kable = "0.43.1"
|
||||
kable = "0.44.0"
|
||||
mqttastic = "0.4.0"
|
||||
jmdns = "3.6.3"
|
||||
qrcode-kotlin = "4.5.0"
|
||||
|
||||
Reference in New Issue
Block a user