Skip to content

fix(ble): retrigger connection when bonding is interrupted#5849

Merged
jamesarich merged 1 commit into
mainfrom
claude/magical-jones-7022d1
Jun 18, 2026
Merged

fix(ble): retrigger connection when bonding is interrupted#5849
jamesarich merged 1 commit into
mainfrom
claude/magical-jones-7022d1

Conversation

@jamesarich

Copy link
Copy Markdown
Collaborator

Why

Selecting an unbonded node and having the bonding flow get interrupted left the device inert — the user had to re-select the node to retrigger a connection.

Root cause: the transport's reconnect loop is persistent and bonds on demand, but it only starts once changeDeviceAddress() runs. For an unbonded device that call was gated behind a clean bond() success, so any bonding hiccup (user-cancel/timeout, an unreliable ACTION_BOND_STATE_CHANGED broadcast, or an OS/GATT-initiated bond already in flight) skipped it and the persistent reconnect loop was never armed.

Changes

🐛 Bug Fixes

  • AndroidScannerViewModel.requestBonding — arm the transport on every bonding outcome except a permissions SecurityException. A flaky or interrupted bond now hands off to the transport's reconnect+backoff loop (which bonds on demand and retries) instead of stranding the device until a manual re-select.

  • AndroidBluetoothRepository.bond — when createBond() returns false, re-check bondState instead of failing outright:

    • BOND_BONDED → resume immediately
    • BOND_BONDING → keep waiting on the receiver for the terminal transition
    • otherwise → fail as before

    This handles the in-flight-bond collision and unreliable bond-state broadcast documented upstream in Kable #111.

Reviewer notes

  • No automated coverage added. Both files are androidMain classes touching BluetoothDevice / BroadcastReceiver, and neither module has a Robolectric / androidUnitTest harness today. Standing one up is a larger, separate effort.
  • Needs real-hardware verification. This is a behavioral BLE change; the meaningful test is selecting an unbonded node, interrupting bonding, and confirming the connection retriggers without re-selecting.
  • Kable itself provides no bonding API (it's intentionally unsupported), so bonding remains entirely app-owned here — the fix leans on the existing transport reconnect loop rather than adding a new mechanism.

Validation

  • ./gradlew spotlessApply spotlessCheck — pass
  • ./gradlew detekt — pass
  • compileAndroidMain for :core:ble and :feature:connections (with --rerun-tasks) — pass

Selecting an unbonded node gated the connection on a clean bond()
success: changeDeviceAddress() (which arms the persistent transport
reconnect loop) only ran when bonding completed cleanly. Any bonding
hiccup — user-cancel/timeout, an unreliable ACTION_BOND_STATE_CHANGED
broadcast, or an OS/GATT-initiated bond already in flight — left the
device inert until the user manually re-selected the node.

- AndroidScannerViewModel.requestBonding: arm the transport on every
  outcome except a permissions SecurityException, handing off to the
  transport's reconnect+backoff loop (which bonds on demand) instead of
  requiring a manual re-select.
- AndroidBluetoothRepository.bond: when createBond() returns false,
  re-check bondState instead of failing outright — BOND_BONDED resumes
  immediately, BOND_BONDING keeps waiting on the receiver. Handles the
  in-flight-bond collision documented in Kable #111.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the bugfix PR tag label Jun 18, 2026
@jamesarich jamesarich added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit 975adce Jun 18, 2026
18 checks passed
@jamesarich jamesarich deleted the claude/magical-jones-7022d1 branch June 18, 2026 16:54
jeremiah-k pushed a commit to jeremiah-k/Meshtastic-Android that referenced this pull request Jun 18, 2026
jamesarich added a commit that referenced this pull request Jun 18, 2026
- AndroidScannerViewModel: keep main's armTransport bonding-retry structure;
  apply the SecurityException + USB string-resource conversions on top; drop the
  now-moot bonding_failed string (main no longer errors on flaky bonding)
- NodeDetailScreens: wrap onRequestPosition/node in rememberUpdatedState for the
  grant LaunchedEffect (LambdaParameterInRestartableEffect)
- spotless: blank lines between multiline when-conditions (TakPermissionUtil,
  ConnectionsScreen)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant