Add error screen no connection on Automotive#6857
Conversation
There was a problem hiding this comment.
Pull request overview
Adds connection availability monitoring to the Automotive (Android Auto) experience so users see a dedicated “No connection” screen instead of an infinite loading state when connectivity is lost.
Changes:
- Introduces a
ConnectionAvailabilityMonitorthat polls WebSocket pings with a grace period before declaring the connection unavailable - Adds an Automotive
NoConnectionScreenand integrates it intoHaCarAppServicescreen stack handling - Adds unit tests covering the monitor’s grace-period and recovery behavior
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| common/src/main/res/values/strings.xml | Adds a new Automotive title string for the no-connection header |
| app/src/main/kotlin/io/homeassistant/companion/android/vehicle/ConnectionAvailabilityMonitor.kt | Implements connection availability monitoring and Hilt binding |
| app/src/main/kotlin/io/homeassistant/companion/android/vehicle/HaCarAppService.kt | Observes availability and pushes/pops the no-connection screen |
| app/src/main/kotlin/io/homeassistant/companion/android/vehicle/NoConnectionScreen.kt | New Automotive error screen UI |
| app/src/test/kotlin/io/homeassistant/companion/android/vehicle/ConnectionAvailabilityMonitorTest.kt | Unit tests for monitor state transitions and grace period |
Comments suppressed due to low confidence (1)
app/src/main/kotlin/io/homeassistant/companion/android/vehicle/HaCarAppService.kt:144
- If the user dismisses NoConnectionScreen via the host back action while the connection is still unavailable, no further Unavailable emissions occur (the flow only emits on state transitions), so the screen may never be shown again until connectivity changes. Consider preventing dismissal of NoConnectionScreen while unavailable, or re-check/push the screen when it is not on top even if the availability state hasn’t changed.
val screenManager = carContext.getCarService(ScreenManager::class.java)
val topIsNoConnection = screenManager.top is NoConnectionScreen
when (state) {
ConnectionAvailability.Unavailable -> if (!topIsNoConnection) {
screenManager.push(NoConnectionScreen(carContext))
}
ConnectionAvailability.Available -> if (topIsNoConnection) {
screenManager.pop()
}
}
| @@ -0,0 +1,88 @@ | |||
| package io.homeassistant.companion.android.vehicle | |||
There was a problem hiding this comment.
Let's not make this specific to vehicle, also it is a public API so I expect to see documentation on the public part of this class (explaining the behavior not the implementation).
| internal val GRACE_PERIOD: Duration = 10.seconds | ||
|
|
||
| internal val HEALTHY_POLL_INTERVAL: Duration = 15.seconds | ||
|
|
||
| internal val DEGRADED_POLL_INTERVAL: Duration = 1.seconds |
There was a problem hiding this comment.
This probably should only be @VisibleForTesting
| if (!serverManager.isRegistered()) { | ||
| true | ||
| } else { | ||
| serverManager.webSocketRepository().sendPing() |
There was a problem hiding this comment.
You are only checking the current active server, it needs to be documented, in the class documentation for instance.
| .setTint(CarColor.DEFAULT) | ||
| .build() | ||
|
|
||
| return MessageTemplate.Builder(carContext.getString(R.string.error_connection_failed_no_network)) |
There was a problem hiding this comment.
You are assuming that when we don't have a WebSocket connection we have no network. It might not be true, the server might simply be down. If you check to the new error screen when we are not able to reach out to the server we show a way to test what is currently wrong check FrontendConnectionErrorStateProvider
We could replicate this or simply change the message to say that the connection to the server is broken maybe?
@jpelgrom might have another opinion on the wording used.
There was a problem hiding this comment.
We could replicate this or simply change the message to say that the connection to the server is broken maybe?
@jpelgrom might have another opinion on the wording used.
Replicating the entire screen feels like a bit much for a car, but a general header "unable to connect" and a short sentence describing the reason, like the main app, would be nice I think. We also don't have to discuss the wording in that case ;)
There was a problem hiding this comment.
Agree that we don't need to replicate, but the message and the logo should not say that there are no network available that's potentially not the issue, so it should be more generic.
| import org.junit.jupiter.api.extension.ExtendWith | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
| @ExtendWith(MainDispatcherJUnit5Extension::class, ConsoleLogExtension::class) |
There was a problem hiding this comment.
| @ExtendWith(MainDispatcherJUnit5Extension::class, ConsoleLogExtension::class) | |
| @ExtendWith(ConsoleLogExtension::class) |
I don't think you need the extension for the MainDispatcher here.
|
|
||
| private fun createMonitor(): ConnectionAvailabilityMonitor = ConnectionAvailabilityMonitorImpl(serverManager) | ||
|
|
||
| private fun stubPings(vararg results: Boolean) { |
There was a problem hiding this comment.
| private fun stubPings(vararg results: Boolean) { | |
| private fun mockPings(vararg results: Boolean) { |
A stub is something dumb, here it's real mocking.
| val webSocketRepository = mockk<WebSocketRepository>() | ||
| val iterator = results.iterator() | ||
| var last = results.last() | ||
| coEvery { webSocketRepository.sendPing() } answers { |
There was a problem hiding this comment.
I think you could use returnsMany instead of using the iterator.
TimoPtr
left a comment
There was a problem hiding this comment.
Thanks for taking this bug, we need to check the wording of the error page and after that we are good to merge.
Summary
Add connection availability monitoring to Automotive
Fixes #6846 — users accessing the app through the Harman Ignite Store were stuck on an infinite loading screen when no internet connection was available, with no error feedback (unlike the standard app).
When the app is running in the car and loses its connection to the Home Assistant server, the user is now shown a dedicated "No connection".
A new ConnectionAvailabilityMonitor polls the Home Assistant WebSocket at a healthy 15 second cadence, switching to a faster 1 second cadence when pings start failing.
To avoid flashing an error screen on transient network blips, the monitor waits for a 10 second grace period before declaring the connection Unavailable; if a ping recovers within that window, no error is surfaced.
Once the connection is back, the monitor emits Available again at the next successful poll and the error screen is dismissed automatically.
When no server is registered yet, the monitor reports Available so the onboarding flow is never blocked by this check.
HaCarAppService observes this state and pushes/pops NoConnectionScreen on the Android Auto screen stack accordingly, keeping the rest of the navigation untouched.
The flow logic (grace period, recovery during grace, healthy→degraded transitions, recovery from unavailable) is covered by unit tests in ConnectionAvailabilityMonitorTest.
Checklist
Screenshots