This commit is contained in:
Dimitris
2026-04-04 13:16:15 +02:00
parent 69b27d3b6c
commit b99ebfd36f
8 changed files with 145 additions and 96 deletions
@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.Location
import android.os.IBinder
import android.util.Log
@@ -29,11 +28,8 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.asLiveData
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.lifecycleScope
import com.kouros.navigation.car.navigation.NavigationService
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.navigation.Simulation
import com.kouros.navigation.car.screen.NavigationListener
import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.NavigationType
@@ -43,30 +39,22 @@ import com.kouros.navigation.car.screen.checkPermission
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
import com.kouros.navigation.data.Constants.homeHohenwaldeck
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.formattedDistance
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
@@ -115,8 +103,11 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
var speedCameras = listOf<Elements>()
var recentPlaces = mutableListOf<Place>()
var lastRouteDate: LocalDateTime = LocalDateTime.now()
var destination = Place()
var notificationActive = false
var navigationService: NavigationService? = null
@@ -129,9 +120,9 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
hasArrived: Boolean,
destinations: MutableList<Destination>,
steps: MutableList<Step>,
destinationTravelEstimate: TravelEstimate?,
stepTravelEstimate: TravelEstimate?,
stepRemainingDistance: Distance?,
destinationTravelEstimate: TravelEstimate,
stepTravelEstimate: TravelEstimate,
stepRemainingDistance: Distance,
shouldShowNextStep: Boolean,
shouldShowLanes: Boolean,
junctionImage: CarIcon?,
@@ -142,11 +133,11 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
isNavigating = isNavigating,
isRerouting = isRerouting,
hasArrived = hasArrived,
destinationTravelEstimate = destinationTravelEstimate!!,
stepTravelEstimate = stepTravelEstimate!!,
destinationTravelEstimate = destinationTravelEstimate,
stepTravelEstimate = stepTravelEstimate,
destinations = destinations,
steps = steps,
stepRemainingDistance = stepRemainingDistance!!,
stepRemainingDistance = stepRemainingDistance,
shouldShowNextStep = shouldShowNextStep,
shouldShowLanes = shouldShowLanes,
junctionImage = junctionImage,
@@ -162,12 +153,6 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
surfaceRenderer.carOrientation,
)
initialLocation = false
navigationViewModel.loadRoute(
carContext,
location,
listOf(homeHohenwaldeck),
surfaceRenderer.carOrientation
)
}
updateLocation(location)
}
@@ -466,7 +451,7 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
private fun handleNavigateIntent(screenManager: ScreenManager) {
screenManager.popToRoot()
screenManager.pushForResult(
SearchScreen(carContext, surfaceRenderer, navigationViewModel, mutableListOf())
SearchScreen(carContext, surfaceRenderer, navigationViewModel, recentPlaces)
) { result ->
// Handle search result if needed
}
@@ -500,7 +485,6 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
updateBearing(location)
checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
surfaceRenderer.updateLocation(location, "")
}
/**
@@ -512,14 +496,13 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
}
}
/**
* Start navigation process.
* Called when user starts navigation
*/
override fun startNavigation() {
Log.d(TAG, "startNavigation")
navigationService!!.startNavigation(route)
navigationService!!.startNavigation(route, destination)
if (notificationActive)
notificationManager.startNotificationService()
}
@@ -621,6 +604,14 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
surfaceRenderer.maxSpeed.value = speed
}
override fun onRecentPlacesReceived(places: List<Place>) {
Log.d(TAG, "onRecentPlacesReceived ${places.size}")
recentPlaces = places.toMutableList()
navigationScreen.recentPlaces = places.toMutableList()
navigationScreen.invalidate()
}
override fun invalidateScreen() {
navigationScreen.invalidate()
}
@@ -647,6 +638,7 @@ class NavigationServiceSession : CarSession(), NavigationListener, NavigationObs
}
navigationViewModel.saveRecent(carContext, place)
destination = place
// routeModel.navState = routeModel.navState.copy(destination = place)
if (preview.isEmpty()) {
navigationViewModel.loadRoute(
@@ -752,6 +752,9 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
surfaceRenderer.maxSpeed.value = speed
}
override fun onRecentPlacesReceived(places: List<Place>) {
}
override fun invalidateScreen() {
navigationScreen.invalidate()
}
@@ -6,6 +6,7 @@ import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.location.Location
import android.location.LocationManager
import android.os.Binder
import android.os.IBinder
import android.text.TextUtils
@@ -16,6 +17,7 @@ import androidx.car.app.CarToast
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance
import androidx.car.app.model.Distance.UNIT_METERS
import androidx.car.app.navigation.NavigationManager
import androidx.car.app.navigation.NavigationManagerCallback
import androidx.car.app.navigation.model.Destination
@@ -29,6 +31,7 @@ import com.kouros.navigation.car.screen.NavigationType
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
@@ -37,6 +40,7 @@ import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.formattedDistance
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
import kotlin.collections.copy
import kotlin.compareTo
@@ -81,6 +85,8 @@ class NavigationService : Service() {
// Manages device GPS location updates
lateinit var deviceLocationManager: DeviceLocationManagerService
var currentLocation = location(0.0, 0.0)
lateinit var navigationViewModel: NavigationViewModel
private lateinit var navigationManager: NavigationManager
@@ -97,16 +103,15 @@ class NavigationService : Service() {
hasArrived: Boolean,
destinations: MutableList<Destination>,
steps: MutableList<Step>,
destinationTravelEstimate: TravelEstimate?,
stepTravelEstimate: TravelEstimate?,
stepRemainingDistance: Distance?,
destinationTravelEstimate: TravelEstimate,
stepTravelEstimate: TravelEstimate,
stepRemainingDistance: Distance,
shouldShowNextStep: Boolean,
shouldShowLanes: Boolean,
junctionImage: CarIcon?,
backGroundColor: CarColor
)
fun updateServiceLocation(location: Location)
}
@@ -225,29 +230,32 @@ class NavigationService : Service() {
}
/** Starts navigation. */
fun startNavigation(route: String) {
fun startNavigation(route: String, destination: Place) {
Log.i(TAG, "Starting Navigation")
startService(Intent(applicationContext, NavigationService::class.java))
routeModel.navState = routeModel.navState.copy(destination = destination)
routeModel.navState = routeModel.navState.copy(routingEngine = 2)
routeModel.startNavigation(route)
if (routeModel.isNavigating()) {
// listener.navigationStateChanged(
// isNavigating = true,
// isRerouting = true,
// hasArrived = false,
// destinations = emptyList<Destination>().toMutableList(),
// steps = emptyList<Step>().toMutableList(),
// destinationTravelEstimate = null,
// stepTravelEstimate = null,
// stepRemainingDistance = null,
// shouldShowNextStep = false,
// shouldShowLanes = false,
// junctionImage = null,
// backGroundColor = CarColor.BLUE
// )
routeModel.updateLocation(currentLocation, navigationViewModel)
listener.navigationStateChanged(
isNavigating = true,
isRerouting = false,
hasArrived = false,
destinations = mutableListOf(routeModel.getDestination()),
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext!!),
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext!!),
steps = routeModel.getSteps(carContext!!),
stepRemainingDistance = routeModel.getDistance(),
shouldShowNextStep = false,
shouldShowLanes = false,
junctionImage = null,
backGroundColor = routeModel.backGroundColor()
)
}
}
/** Starts navigation. */
fun stopNavigation() {
if (autoDriveEnabled) {
@@ -261,9 +269,9 @@ class NavigationService : Service() {
hasArrived = false,
destinations = emptyList<Destination>().toMutableList(),
steps = emptyList<Step>().toMutableList(),
destinationTravelEstimate = null,
stepTravelEstimate = null,
stepRemainingDistance = null,
destinationTravelEstimate = routeModel.travelEstimate(carContext!!, 0.0, 0),
stepTravelEstimate = routeModel.travelEstimate(carContext!!, 0.0, 0),
stepRemainingDistance = Distance.create(0.0, UNIT_METERS),
shouldShowNextStep = false,
shouldShowLanes = false,
junctionImage = null,
@@ -275,6 +283,7 @@ class NavigationService : Service() {
fun updateLocation(location: Location) {
Log.d(TAG, "updateLocation")
currentLocation = location
if (routeModel.isNavigating()) {
routeModel.updateLocation(location, navigationViewModel)
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
@@ -296,49 +305,33 @@ class NavigationService : Service() {
) {
return
}
val travelEstimateTrip = routeModel.travelEstimateTrip(carContext!!, distanceMode)
val travelEstimateStep = routeModel.travelEstimateStep(carContext!!, distanceMode)
val steps = mutableListOf<Step>()
val destination = Destination.Builder()
.setName(routeModel.navState.destination.name)
.setAddress(routeModel.navState.destination.street)
.build()
val distance =
formattedDistance(0, routeModel.routeCalculator.leftStepDistance())
steps.add(routeModel.currentStep(carContext!!))
if (routeModel.navState.nextStep) {
steps.add(routeModel.nextStep(carContext = carContext!!))
}
listener.navigationStateChanged(
isNavigating = routeModel.isNavigating(),
isRerouting = false,
hasArrived = routeModel.isArrival(),
destinationTravelEstimate = travelEstimateTrip,
stepTravelEstimate = travelEstimateStep,
destinations = mutableListOf(destination),
steps = steps,
stepRemainingDistance = Distance.create(distance.first, distance.second),
destinations = mutableListOf(routeModel.getDestination()),
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext!!),
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext!!),
steps = routeModel.getSteps(carContext!!),
stepRemainingDistance = routeModel.getDistance(),
shouldShowNextStep = false,
shouldShowLanes = true,
shouldShowLanes = false,
junctionImage = null,
backGroundColor = routeModel.backGroundColor()
backGroundColor = routeModel.backGroundColor()
)
/**
* Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status.
*/
val tripBuilder = Trip.Builder()
tripBuilder.addDestination(
destination,
travelEstimateTrip
routeModel.getDestination(),
routeModel.getTravelEstimateTrip(carContext!!)
)
tripBuilder.setLoading(false)
tripBuilder.setCurrentRoad(destination.name.toString())
tripBuilder.addStep(steps.first(), travelEstimateStep)
tripBuilder.setCurrentRoad(routeModel.getDestination().name.toString())
tripBuilder.addStep(routeModel.getSteps(carContext!!).first(), routeModel.getTravelEstimateStep(carContext!!))
navigationManager.updateTrip(tripBuilder.build())
}
@@ -18,6 +18,7 @@ import androidx.car.app.model.DateTimeWithZone
import androidx.car.app.model.Distance
import androidx.car.app.model.DurationSpan
import androidx.car.app.model.ForegroundCarColorSpan
import androidx.car.app.navigation.model.Destination
import androidx.car.app.navigation.model.Lane
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver
@@ -99,7 +100,6 @@ class RouteCarModel : RouteModel() {
}
fun travelEstimate(carContext: CarContext, timeLeft: Double, distanceMode: Int): TravelEstimate {
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance())
@@ -134,6 +134,37 @@ class RouteCarModel : RouteModel() {
}
return travelBuilder.build()
}
fun getSteps(carContext: CarContext): MutableList<Step> {
val steps = mutableListOf<Step>()
steps.add(currentStep(carContext))
if (navState.nextStep) {
steps.add(nextStep(carContext = carContext))
}
return steps
}
fun getDistance(): Distance {
val distance =
formattedDistance(0, routeCalculator.leftStepDistance())
return Distance.create(distance.first, distance.second)
}
fun getTravelEstimateTrip(carContext: CarContext): TravelEstimate {
return travelEstimateTrip(carContext, 0)
}
fun getTravelEstimateStep(carContext: CarContext): TravelEstimate {
return travelEstimateStep(carContext, 0)
}
fun getDestination(): Destination {
return Destination.Builder()
.setName(navState.destination.name)
.setAddress(navState.destination.street)
.build()
}
private fun createDelay(delay: Int): CarText {
val delayBuilder = SpannableStringBuilder()
delayBuilder.append(
@@ -154,10 +154,11 @@ open class NavigationScreen(
.setDestinationTravelEstimate(destinationTravelEstimate)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(
mapActionStrip(carContext,
mapActionStrip(
carContext,
surfaceRenderer.viewStyle,
{ zoomPlus() }, { zoomMinus() }, {
Action.Builder()
Action.Builder()
.setIcon(createCarIcon(carContext, R.drawable.ic_recenter_24))
.setFlags(0)
.setOnClickListener {
@@ -175,17 +176,18 @@ open class NavigationScreen(
* Creates and returns a template for the default view state.
*/
private fun navigationView(actionStripBuilder: ActionStrip.Builder): Template {
val mapActionStrip = mapActionStrip(carContext,
surfaceRenderer.viewStyle,
{ zoomPlus() }, { zoomMinus() }, {
createAction(
carContext = carContext, R.drawable.ic_recenter_24,
0,
onClickAction = {
surfaceRenderer.viewStyle = ViewStyle.VIEW
invalidate()
})
})
val mapActionStrip = mapActionStrip(
carContext,
surfaceRenderer.viewStyle,
{ zoomPlus() }, { zoomMinus() }, {
createAction(
carContext = carContext, R.drawable.ic_recenter_24,
0,
onClickAction = {
surfaceRenderer.viewStyle = ViewStyle.VIEW
invalidate()
})
})
return NavigationTemplate.Builder()
.setBackgroundColor(backGroundColor)
.setActionStrip(actionStripBuilder.build())
@@ -241,7 +243,8 @@ open class NavigationScreen(
.setBackgroundColor(backGroundColor)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(
mapActionStrip(carContext,
mapActionStrip(
carContext,
surfaceRenderer.viewStyle,
{ zoomPlus() }, { zoomMinus() }, {
createAction(
@@ -302,7 +305,8 @@ open class NavigationScreen(
val builder = MapWithContentTemplate.Builder()
.setContentTemplate(contentTemplate)
.setActionStrip(
mapActionStrip(carContext,
mapActionStrip(
carContext,
ViewStyle.VIEW,
{ settingsAction() },
{
@@ -341,10 +345,12 @@ open class NavigationScreen(
*/
fun getRoutingInfo(): RoutingInfo {
val routingInfo = RoutingInfo.Builder()
.setCurrentStep(
if (steps.isNotEmpty()) {
routingInfo.setCurrentStep(
steps.first(),
stepRemainingDistance
)
}
if (shouldShowNextStep && steps.size > 1) {
routingInfo.setNextStep(steps[1])
}
@@ -492,7 +498,6 @@ open class NavigationScreen(
}
/**
* Updates navigation state with the current location, checks for arrival, and traffic updates.
*/
@@ -1,7 +1,6 @@
package com.kouros.navigation.car.screen.observers
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements
/**
@@ -26,6 +25,8 @@ interface NavigationObserverCallback {
/** Called when max speed is updated */
fun onMaxSpeedReceived(speed: Int)
fun onRecentPlacesReceived(places: List<Place>)
/** Called to request UI invalidation/refresh */
fun invalidateScreen()
@@ -19,12 +19,18 @@ class NavigationObserverManager(
val speedCameraObserver = SpeedCameraObserver(callback)
val maxSpeedObserver = MaxSpeedObserver(callback)
val recentPlacesObserver = RecentPlacesObserver(callback)
fun attachAllObservers(session: CarSession) {
viewModel.route.observe(session, routeObserver)
viewModel.traffic.observe(session, trafficObserver)
viewModel.placeLocation.observe(session, placeSearchObserver)
viewModel.speedCameras.observe(session, speedCameraObserver)
viewModel.maxSpeed.observe(session, maxSpeedObserver)
viewModel.recentPlaces.observe(session, recentPlacesObserver)
}
/**
@@ -0,0 +1,18 @@
package com.kouros.navigation.car.screen.observers
import androidx.lifecycle.Observer
import com.kouros.navigation.data.Place
/**
* Observer for route updates. Triggers navigation start when a non-empty route is received.
*/
class RecentPlacesObserver(
private val callback: NavigationObserverCallback
) : Observer<List<Place>> {
override fun onChanged(value: List<Place>) {
if (value.isNotEmpty()) {
callback.onRecentPlacesReceived(value)
}
}
}