Skip to content

Add error screen no connection on Automotive#6857

Open
cddu33 wants to merge 4 commits into
home-assistant:mainfrom
cddu33:feature/add-error-screen-connection
Open

Add error screen no connection on Automotive#6857
cddu33 wants to merge 4 commits into
home-assistant:mainfrom
cddu33:feature/add-error-screen-connection

Conversation

@cddu33
Copy link
Copy Markdown
Contributor

@cddu33 cddu33 commented May 17, 2026

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

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.

Screenshots

image

@cddu33 cddu33 marked this pull request as ready for review May 17, 2026 11:46
Copilot AI review requested due to automatic review settings May 17, 2026 11:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ConnectionAvailabilityMonitor that polls WebSocket pings with a grace period before declaring the connection unavailable
  • Adds an Automotive NoConnectionScreen and integrates it into HaCarAppService screen 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment on lines +32 to +36
internal val GRACE_PERIOD: Duration = 10.seconds

internal val HEALTHY_POLL_INTERVAL: Duration = 15.seconds

internal val DEGRADED_POLL_INTERVAL: Duration = 1.seconds
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should only be @VisibleForTesting

if (!serverManager.isRegistered()) {
true
} else {
serverManager.webSocketRepository().sendPing()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Image

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could use returnsMany instead of using the iterator.

Copy link
Copy Markdown
Member

@TimoPtr TimoPtr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this bug, we need to check the wording of the error page and after that we are good to merge.

@TimoPtr TimoPtr requested a review from jpelgrom May 18, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infinite loading on no connection in automotive

4 participants