fix(devices): preserve connection and transport on launch timeout for native reconnect (#20777)

Merged
DR
2d ago
+201-4

Summary

Resolves #20777.

Problem

At app launch, DeviceService.ensureConnection makes an initial connection attempt to the saved pendant. If the pendant is out of range or powered off for 60 seconds:

  1. NativeBleTransport.connect() times out and rethrows TimeoutException.
  2. DeviceConnection.connect() wraps this in DeviceConnectionException.
  3. In DeviceService._connectToDevice, the catch block was tearing down the connection unconditionally (disconnect(), transport.dispose(), and removing from _connections).
  4. Disposing the transport calls BleBridge.instance.unregisterPeripheral(), which removes _deviceReadyCallbacks.
  5. When the user later brings the pendant in range or powers it on, native BLE reconnects and emits onDeviceReady, but finds no Dart callback. The app stays permanently disconnected and never captures audio until manual user reconnection or relaunch.

Solution

  1. DeviceConnectionException (device_connection.dart):
    • Added DeviceConnectionTimeoutException and isTimeout property to distinguish timeout errors from failed device-specific initialization.
    • Wrapped TimeoutException in DeviceConnectionTimeoutException during DeviceConnection.connect().
  2. DeviceService._connectToDevice (devices.dart):
    • Guarded the teardown path (disconnect(), transport.dispose(), and cache removal) when isTimeout is true.
    • Preserves the cached connection and registered NativeBleTransport so native background auto-reconnection persists and routes late readiness events back into Dart.
    • Non-timeout failures (such as a broken GATT link or failed device initialization) continue to tear down partial links as before.
  3. Tests:
    • Extended app/test/services/device_service_connection_failure_test.dart to verify that launch timeouts preserve the connection and transport, while initialization errors still tear down.
    • Added hermetic regression test app/test/services/devices/saved_pendant_launch_timeout_reconnect_test.dart driving real DeviceService, FriendPendantDeviceConnection, NativeBleTransport, and BleBridge with fake_async advancing the 60s timeout, proving the transport stays registered and reconnects into DeviceConnectionState.connected on subsequent onDeviceReady.