fix(ble): Bound Android bonding wait#5967
Merged
Merged
Conversation
Add a hard timeout around Android BLE bonding so the app cannot remain suspended forever when the platform does not deliver a terminal ACTION_BOND_STATE_CHANGED broadcast. After the timeout expires, re-check the platform bondState before failing. This preserves successful pairings that Android recorded even if the broadcast was missed, while still surfacing a clear failure when the device never becomes bonded. Keep the BluetoothRepository API unchanged and continue refreshing repository state after the bonding attempt resolves.
Centralize bonding receiver completion and unregistration so the receiver is cleaned up across success, failure, timeout, cancellation, and setup failures after registration. Route post-registration bondState/createBond exceptions through the suspended result instead of allowing them to bypass cleanup. This prevents receiver leaks when Android permission or Bluetooth APIs throw during the bonding setup path. Preserve the existing in-flight bonding behavior: createBond() returning false while Android reports BOND_BONDING continues waiting for a terminal result instead of failing immediately.
Clarify the comments around the bounded bonding wait so the implementation reads as intentional rather than as a loose boolean sentinel. Document why createBond() returning false is not always terminal on Android and why the code re-checks bondState directly for Kable meshtastic#111-style unreliable bond broadcasts. No runtime behavior changes.
Rewrap the AndroidBluetoothRepository bonding comment so the branch stays within the repository lint and formatting limits. No runtime behavior changes.
Poll Android bondState while the app waits for the terminal bonding signal so missed or delayed ACTION_BOND_STATE_CHANGED broadcasts no longer force users to sit through the full 30-second timeout after entering a PIN. Use an interval timeout around the deferred receiver result instead of sleeping unconditionally. That lets receiver completion resume the coroutine immediately while still periodically checking the platform bond state when no broadcast arrives. Grant BLUETOOTH_CONNECT in the post-registration race test and keep the repository implementation within detekt's method/function limits by extracting the bond setup and wait helpers without changing the public BluetoothRepository API.
jamesarich
approved these changes
Jun 26, 2026
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).
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 Android BLE bonding from waiting indefinitely when the platform does not deliver a terminal bond-state broadcast.
The Android bonding flow waits for
ACTION_BOND_STATE_CHANGEDafter callingcreateBond(). On some Android devices, that broadcast can be delayed, dropped, or missed when the pairing dialog is dismissed or bonding completes outside the receiver path. In that case the coroutine waiting for bonding can remain suspended, leaving the app stuck after PIN entry and preventing the selected device address from being armed.This change keeps the receiver-based bonding path, but adds a 30-second upper bound around the wait. It also re-checks the platform bond state before starting a new bond, while waiting, and again before failing.
The periodic re-check matters because Android may already report the device as bonded even when the terminal broadcast was missed. In that case the app now completes the bond wait without sitting on the full timeout.
This is the base bonding hardening for the related follow-ups in #5969 and PR #. #5969 uses the bounded/final-state-checked bond result to avoid immediate duplicate UI-side bonding retries after pairing failure. PR #5973 handles the remaining transport-side case by stopping GATT setup when transport-side bonding fails and Android still reports the device as not bonded.
Key Changes
AndroidBluetoothRepository.bond().bondStateafter receiver registration before callingcreateBond().bondStatewhile waiting for Android bonding to complete.BOND_BONDED, even if the terminal broadcast was missed.bondStateagain when the timeout expires.Testing
bondStatebecomes bonded without a broadcast.Migration Notes