fix(ble): Avoid duplicate bonding retries after pairing failure#5969
Merged
jamesarich merged 4 commits intoJun 26, 2026
Merged
Conversation
86c3ae1 to
5f42093
Compare
jeremiah-k
added a commit
to jeremiah-k/Meshtastic-Android
that referenced
this pull request
Jun 26, 2026
When BleRadioTransport bonds before connecting and bond() throws, re-check isBonded before continuing into GATT setup: - bond() succeeds: continue (unchanged) - bond() throws + isBonded true: continue (late/flaky terminal bond) - bond() throws + isBonded false: fail fast with RadioNotConnectedException, let BleReconnectPolicy own retry/backoff instead of hitting cryptic GATT status 5/133 later CancellationException is preserved (rethrown before the generic catch). Follow-up to meshtastic#5967 (bounded bond wait) and meshtastic#5969 (UI retry path).
Stop arming the BLE transport after a generic Android pairing failure so the transport does not immediately call bond() again and trigger duplicate PIN prompts or platform bonding cooldown. This supersedes the earlier interrupted-bonding fallback behavior now that Android bonding waits are timeout-hardened. Successful bonding and permission failures keep their existing semantics, while ordinary pairing failures now surface a retry warning and wait for explicit user action.
Handle the Android edge case where bond() reports an error even though the platform has already recorded the device as bonded. Before treating a generic bonding error as terminal, re-check the repository bond state. If Android now reports the device bonded, select the device and arm the transport because pairing actually succeeded despite the app-side signal loss. Add fake repository support for recording a bond before throwing so the ViewModel behavior is covered without depending on Android framework timing.
Tighten the generic bonding failure test so it asserts the exact retry warning instead of only checking for any non-null error. Keep the fake bonding implementation deterministic by recording the eventual error and throwing once after outcome handling. This preserves the same external behavior while keeping the fake easier to extend for bonding edge cases.
Cover the requestBonding cancellation path so CancellationException cannot be swallowed by the generic bonding failure handler. The regression check verifies that a cancelled bond attempt does not arm the BLE transport and does not surface the retry warning intended for ordinary pairing failures.
5f42093 to
ba1750e
Compare
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This pull request prevents the app from immediately starting a second bonding attempt after the user-facing Android pairing flow fails.
Earlier behavior armed the BLE transport after a generic bonding failure so interrupted bonding could still progress through the transport path. In practice, that also allowed the transport to immediately call
bond()again when the device was still not bonded. On Android, that can produce a second PIN dialog or hit platform bonding cooldown/throttling after a cancelled, rejected, or timed-out pairing attempt.This follows the bonding timeout hardening in #5967. That PR makes Android bonding attempts finite, checks for completed bonding even when the terminal broadcast is missed, and re-checks the final platform bond state before failing.
With that in place, this PR changes the UI-side failure behavior so an incomplete pairing attempt surfaces a retry warning instead of immediately arming the transport and triggering another
bond()call.If Android records the device as bonded even though the app-side bond call reports an error, the ViewModel still proceeds with selecting the device.
The related transport-side follow-up in PR #5973 handles the remaining lower-level case: if transport-side bonding fails and Android still reports the device as not bonded, the BLE transport stops that connection attempt before entering GATT setup.
Key Changes
Testing
AndroidScannerViewModelBondingTestcoverage proving successful bonding still selects the device.changeDeviceAddress()and surface a retry warning.CancellationExceptionis not treated as a generic bonding failure.Migration Notes