mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-26 22:45:30 -04:00
fix(ble): retrigger connection when bonding is interrupted (#5849)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -142,10 +142,31 @@ class AndroidBluetoothRepository(
|
||||
}
|
||||
|
||||
if (!remoteDevice.createBond()) {
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
} catch (ignored: Exception) {}
|
||||
if (cont.isActive) cont.resumeWith(Result.failure(Exception("Failed to initiate bonding")))
|
||||
// createBond() returns false when a bond is already in flight (initiated by the OS or
|
||||
// triggered by a GATT operation hitting a secured characteristic) or already established.
|
||||
// The ACTION_BOND_STATE_CHANGED broadcast is unreliable on some devices (see Kable #111),
|
||||
// so re-check bondState directly rather than failing the whole flow.
|
||||
when (remoteDevice.bondState) {
|
||||
android.bluetooth.BluetoothDevice.BOND_BONDED -> {
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
} catch (ignored: Exception) {}
|
||||
if (cont.isActive) cont.resume(Unit)
|
||||
}
|
||||
|
||||
android.bluetooth.BluetoothDevice.BOND_BONDING -> {
|
||||
// Bond already in progress; leave the receiver registered to resolve it on the
|
||||
// terminal BOND_BONDED / BOND_NONE transition instead of treating this as a failure.
|
||||
Logger.d { "createBond() returned false but bonding is already in progress" }
|
||||
}
|
||||
|
||||
else -> {
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
} catch (ignored: Exception) {}
|
||||
if (cont.isActive) cont.resumeWith(Result.failure(Exception("Failed to initiate bonding")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateBluetoothState()
|
||||
|
||||
@@ -68,26 +68,34 @@ class AndroidScannerViewModel(
|
||||
Logger.i { "Starting bonding for ${entry.device.address.anonymize}" }
|
||||
viewModelScope.launch {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
try {
|
||||
bluetoothRepository.bond(entry.device)
|
||||
Logger.i { "Bonding complete for ${entry.device.address.anonymize}, selecting device..." }
|
||||
changeDeviceAddress(entry.fullAddress)
|
||||
} catch (ex: SecurityException) {
|
||||
Logger.w(ex) { "Bonding failed for ${entry.device.address.anonymize} Permissions not granted" }
|
||||
serviceRepository.setErrorMessage(
|
||||
text = "Bonding failed: ${ex.message} Permissions not granted",
|
||||
severity = Severity.Warn,
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
// Bonding is often flaky and can fail for many reasons (timeout, user cancel, etc)
|
||||
val message = ex.message ?: ""
|
||||
if (message.contains("Received bond state changed 11")) {
|
||||
// This is a known issue where bonding is still in progress, ignore as error
|
||||
Logger.d { "Bonding still in progress for ${entry.device.address.anonymize}" }
|
||||
} else {
|
||||
Logger.w(ex) { "Bonding failed for ${entry.device.address.anonymize}" }
|
||||
serviceRepository.setErrorMessage(text = "Bonding failed: ${ex.message}", severity = Severity.Warn)
|
||||
val armTransport =
|
||||
try {
|
||||
bluetoothRepository.bond(entry.device)
|
||||
Logger.i { "Bonding complete for ${entry.device.address.anonymize}, selecting device..." }
|
||||
true
|
||||
} catch (ex: SecurityException) {
|
||||
// No BLUETOOTH_CONNECT permission — connecting would fail the same way, so surface the
|
||||
// error and do not arm the transport.
|
||||
Logger.w(ex) { "Bonding failed for ${entry.device.address.anonymize} Permissions not granted" }
|
||||
serviceRepository.setErrorMessage(
|
||||
text = "Bonding failed: ${ex.message} Permissions not granted",
|
||||
severity = Severity.Warn,
|
||||
)
|
||||
false
|
||||
} catch (ex: Exception) {
|
||||
// Bonding is flaky and can fail for many reasons: user cancel/timeout, an unreliable
|
||||
// ACTION_BOND_STATE_CHANGED broadcast, or an OS/GATT-initiated bond already in flight
|
||||
// (see Kable #111). Don't treat any of these as terminal — arm the transport anyway so
|
||||
// its persistent reconnect loop (which bonds on demand and retries with backoff) can
|
||||
// converge, instead of leaving the device inert until the user re-selects it.
|
||||
Logger.w(ex) {
|
||||
"Bonding did not complete cleanly for ${entry.device.address.anonymize}; " +
|
||||
"arming transport to retry"
|
||||
}
|
||||
true
|
||||
}
|
||||
if (armTransport) {
|
||||
changeDeviceAddress(entry.fullAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user