diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt index 0354f22..79b11d3 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt @@ -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 diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/host/CarAppConnection.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/host/CarAppConnection.kt index f9eca14..52ef34d 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/host/CarAppConnection.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/host/CarAppConnection.kt @@ -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 = _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