voice: a new destination mid-route ends the old route first instead of being ignored; switching or closing map apps ends the old app's route

This commit is contained in:
Seb3thehacker committed 2026-10-14 14:24:47 -07:00
1 parent 9f982875fe
commit 7ffa203221
2 files changed
+46 -1

No files matched your search

@@ -88,15 +88,22 @@ object CarServices {
if (destination != null && nav.connectedComponent == app.component) {
nav.navigateTo(destination)
} else {
// Another map app taking over ends the old one's route, or it keeps guiding unseen.
if (nav.connectedComponent != app.component) endRoute()
nav.connect(app, frame.width, frame.height, frame.densityDpi, destination)
}
}
fun stopNav() {
navStopped = true
endRoute()
nav.disconnect()
}
private fun endRoute() {
if (nav.navigating) nav.stopNavigating()
}
/** Undoes [stopNav]'s hold so the last map app comes back, same as it would after a restart. */
suspend fun reconnectNav(frame: CarEnvironment.Frame) {
navStopped = false
@@ -240,6 +240,9 @@ class CarAppConnection(private val context: Context) {
this.carApp = null
appManager = null
navigationManager = null
navigating = false
main.removeCallbacks(sendReroute)
reroute = null
_panMode.value = false
surfaceCallback = null
component = null
@@ -313,14 +316,41 @@ class CarAppConnection(private val context: Context) {
/**
* Sends a fresh destination to an app that's already running, instead of tearing it down
* and rebuilding the whole session - see [app.seb3thehacker.gearslip.car.CarServices.connectNav].
*
* An app already on a route ends it first: Organic Maps ignored a new destination mid-route
* and kept the old directions. The new one goes once the app says the route ended, or after
* [REROUTE_WAIT_MS] if it never does.
*/
fun navigateTo(destination: String) {
if (carApp == null) return
main.removeCallbacks(sendReroute)
if (!navigating) {
reroute = null
sendDestination(destination)
return
}
reroute = destination
stopNavigating()
main.postDelayed(sendReroute, REROUTE_WAIT_MS)
}
private fun sendDestination(destination: String) {
val app = carApp ?: return
call("onNewIntent", onValue = {}) {
app.onNewIntent(navigateIntent(destination), it)
}
}
/** A destination waiting for the current route to end; see [navigateTo]. */
private var reroute: String? = null
private val sendReroute = Runnable {
reroute?.let { reroute = null; sendDestination(it) }
}
/** Whether the app has said it's on a route, between navigationStarted and navigationEnded. */
@Volatile var navigating = false
private set
private fun onCreated() {
val app = carApp ?: return
GearslipLog.i("host: created; starting")
@@ -609,10 +639,15 @@ class CarAppConnection(private val context: Context) {
val trip: StateFlow<androidx.car.app.navigation.model.Trip?> = _trip.asStateFlow()
private val navigationHost = object : INavigationHost.Stub() {
override fun navigationStarted() = GearslipLog.i("host: navigation started")
override fun navigationStarted() {
GearslipLog.i("host: navigation started")
navigating = true
}
override fun navigationEnded() {
GearslipLog.i("host: navigation ended")
navigating = false
_trip.value = null
main.post { if (reroute != null) { main.removeCallbacks(sendReroute); sendReroute.run() } }
}
override fun updateTrip(trip: Bundleable?) {
_trip.value = runCatching { trip?.get() as? androidx.car.app.navigation.model.Trip }.getOrNull()
@@ -791,6 +826,9 @@ class CarAppConnection(private val context: Context) {
/** The newest template API this host claims to understand. */
const val HOST_API_LEVEL = CarAppApiLevels.LEVEL_8
/** How long a new destination waits for the old route to end; see [navigateTo]. */
const val REROUTE_WAIT_MS = 1_500L
/** Context.BIND_FOREGROUND_SERVICE; the constant itself is hidden from the SDK. */
const val BIND_FOREGROUND_SERVICE = 0x04000000
}