fix(ble): Fail bonding promptly when polled state returns none#5982
Merged
jamesarich merged 4 commits intoJun 27, 2026
Merged
Conversation
Android can miss the terminal BOND_NONE broadcast after a failed pairing attempt. That left the app waiting until the full bonding timeout even when the platform had already returned the device to BOND_NONE. Track whether bonding has been observed or newly initiated, then let the polling loop fail promptly on BOND_NONE only after bonding was in flight. Keep the existing BOND_BONDED polling success path and preserve a short initial grace poll for createBond() returning true before Android reports BOND_BONDING. Add host-test coverage for missed terminal failure broadcasts and for the initial BOND_NONE race guard.
The grace-poll race test needs to observe BOND_NONE until the production polling code reaches the intended checkpoint. Robolectric's bond shadow may advance state as a side effect of createBond(), so reset the shadow after launching bond() before advancing virtual time. This keeps the test focused on the initial BOND_NONE grace behavior rather than a shadow-driven transition out of the unbonded state.
The early BOND_NONE polling change introduced an extra private helper, pushing AndroidBluetoothRepository over the core:ble TooManyFunctions threshold. Inline the existing-bond observation branch back into startOrObserveBond so the class stays within its detekt budget without adding a suppression. Run :core:ble:spotlessApply so the Kotlin formatting matches CI, including the BondWaitStart class signature and updated host-test setup.
Add focused Android host coverage for createBond() returning true while Android initially reports BOND_NONE, including persistent BOND_NONE after the grace window and BONDING followed by a polled terminal BOND_NONE. Share the receiver and polling failure message through a single internal constant, add debug logging for polling-based failures, and document the fixed two-poll grace used to avoid failing a slow BOND_NONE to BOND_BONDING transition immediately. Clarify the Robolectric createBond() shadow reset needed to model Android's asynchronous BOND_NONE to BOND_BONDING transition and document the polling state-machine invariant for terminal BOND_NONE handling.
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 makes Android BLE bonding fail promptly when the platform bond state returns to
BOND_NONEafter bonding was observed in progress, even if Android does not deliver the terminal bond-state broadcast.This builds on the recent BLE bonding work in #5967, #5969, and #5973: bonding waits are now finite, failed user-facing pairing waits for an explicit retry instead of immediately arming the transport, and the BLE transport no longer continues into GATT setup after failed bonding when Android still reports the device as not bonded.
This change handles the remaining app-side wait case. Android may already know bonding failed, but the app may miss the terminal
ACTION_BOND_STATE_CHANGEDfailure broadcast. Before this change, the polling path only completed early when Android reportedBOND_BONDED. If pairing failed and polling sawBOND_NONE, the app could still wait until the full bonding timeout before surfacing the failure.With this change, polling can now also fail the bond early when it is safe to treat
BOND_NONEas terminal.Key Changes
BOND_BONDED.BOND_NONEafter bonding was observed in progress.createBond()returns true so a slowBOND_NONE -> BOND_BONDINGtransition is not failed immediately.BOND_NONEstate as an immediate failure.Testing
BOND_NONE.BOND_NONEafter the grace window fails.BOND_BONDINGfollowed by polledBOND_NONEfails.BOND_NONEbroadcast is ignored while a later polled terminalBOND_NONEstill fails.BOND_BONDED.Migration Notes