Merge pull request #8 from Brian5647/fix-map-freeze

fix the drawing after coming back from the media menu to navigation app
This commit is contained in:
Seb3thehacker authored and GitHub committed 2026-10-01 12:19:40 -07:00
commit 42785ce6be
2 files changed
+40 -19

No files matched your search

@@ -83,12 +83,16 @@ fun CarHome() {
val navigator = LocalCarNavigator.current
val panel = navigator.sidePanel
// Coming back to the map from another screen: make sure the nav app is drawing into a live
// surface. The first composition is skipped, since the surface is only just being lent.
// Leaving the map for another screen: hand the nav app a fresh surface while it's covered, so
// it has redrawn by the time the map comes back - a new surface shows black until the app's
// next frame. Coming back only replaces one that died meanwhile. Nothing happens before Home
// has first been shown, since the surface is only just being lent.
val onHome = navigator.current == CarScreen.Home
val seenHome = remember { booleanArrayOf(false) }
LaunchedEffect(onHome) {
if (onHome && seenHome[0]) CarServices.nav.refreshSurface()
if (seenHome[0]) {
if (onHome) CarServices.nav.replaceDeadSurface() else CarServices.nav.renewSurface()
}
if (onHome) seenHome[0] = true
}
val splitScreen = phase == CarMedia.Phase.READY && now.isActive && panel != SidePanel.NONE
@@ -455,31 +455,48 @@ class CarAppConnection(private val context: Context) {
private val _surfaceEpoch = MutableStateFlow(0)
/** Bumped when the lent surface turns out to be dead, so the stage builds a fresh one. */
/** Bumped to throw the lent surface away, so the stage builds a fresh one. */
val surfaceEpoch: StateFlow<Int> = _surfaceEpoch.asStateFlow()
/**
* Lends the app its surface again: called when the map comes back into view. An app that
* draws through its own VirtualDisplay (Organic Maps does) can be left pointing at a surface
* that has since died, and draws black until it's told about a live one. Taking the surface
* back and lending it again repoints it; a surface that has itself died is replaced.
* Lends the app a fresh surface: called as the map goes out of view, so the app has redrawn
* on the new one by the time it comes back. An app that draws through its own VirtualDisplay
* (Organic Maps does) can be left pointing at a surface that has since died, and draws black
* until it's told about a live one.
*
* Always a new surface, never the same one lent again: such an app (Flitsmeister does) builds
* its new display on the re-lent surface before releasing the old, and the old one's
* disconnect then takes the shared BufferQueue's connection with it - the map freezes on its
* last frame for good. The stage's key on [surfaceEpoch] swaps in a new SurfaceView, which
* lends itself as it comes up.
*/
fun refreshSurface() {
fun renewSurface() {
main.post {
val current = lent ?: return@post
if (!current.surface.isValid) {
GearslipLog.w("host: the lent surface died; building a new one")
lent = null
sendSurfaceDestroyed(current)
_surfaceEpoch.value = _surfaceEpoch.value + 1
return@post
}
GearslipLog.i("host: lending the surface again as the map comes back")
sendSurfaceDestroyed(current)
sendSurfaceAvailable()
GearslipLog.i("host: building a fresh surface as the map goes out of view")
replaceSurface(current)
}
}
/**
* Called as the map comes back into view: replaces the surface only if it died while the map
* was covered, since a new one shows black until the app's next frame.
*/
fun replaceDeadSurface() {
main.post {
val current = lent ?: return@post
if (current.surface.isValid) return@post
GearslipLog.w("host: the lent surface died; building a new one")
replaceSurface(current)
}
}
private fun replaceSurface(current: Lent) {
lent = null
sendSurfaceDestroyed(current)
_surfaceEpoch.value = _surfaceEpoch.value + 1
}
private fun sendSurfaceAvailable() {
val callback = surfaceCallback ?: return
val surface = lent ?: return