Auto Drive Simulation
This commit is contained in:
@@ -4,7 +4,6 @@ import android.Manifest.permission
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.util.Log
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
@@ -21,9 +20,10 @@ 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.NavigationUtils
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.navigation.Simulation
|
||||
import com.kouros.navigation.car.screen.NavigationScreen
|
||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||
import com.kouros.navigation.car.screen.SearchScreen
|
||||
@@ -42,8 +42,6 @@ import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
import org.maplibre.compose.expressions.dsl.step
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
/**
|
||||
@@ -76,6 +74,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
lateinit var textToSpeechManager: TextToSpeechManager
|
||||
|
||||
var autoDriveEnabled = false
|
||||
|
||||
val simulation = Simulation()
|
||||
/**
|
||||
* Lifecycle observer for managing session lifecycle events.
|
||||
* Cleans up resources when the session is destroyed.
|
||||
@@ -91,9 +92,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
if (::deviceLocationManager.isInitialized) {
|
||||
deviceLocationManager.stopLocationUpdates()
|
||||
}
|
||||
if (::textToSpeechManager.isInitialized) {
|
||||
textToSpeechManager.cleanup()
|
||||
}
|
||||
if (::textToSpeechManager.isInitialized) {
|
||||
textToSpeechManager.cleanup()
|
||||
}
|
||||
Log.i(TAG, "NavigationSession destroyed")
|
||||
}
|
||||
}
|
||||
@@ -200,19 +201,19 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
* Initializes managers for rendering, sensors, and location.
|
||||
*/
|
||||
private fun initializeManagers() {
|
||||
|
||||
navigationManager = carContext.getCarService(NavigationManager::class.java)
|
||||
navigationManager.setNavigationManagerCallback(object : NavigationManagerCallback {
|
||||
override fun onAutoDriveEnabled() {
|
||||
// Called when the app should simulate navigation (e.g., for testing)
|
||||
// Implement your simulation logic here
|
||||
Log.d("CarApp", "Auto Drive Enabled")
|
||||
autoDriveEnabled = true
|
||||
}
|
||||
|
||||
override fun onStopNavigation() {
|
||||
// Called when the user stops navigation in the car screen
|
||||
Log.d("CarApp", "Stop Navigation Requested")
|
||||
// Stop turn-by-turn logic and clean up
|
||||
routeModel.stopNavigation()
|
||||
autoDriveEnabled = false
|
||||
}
|
||||
})
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||
@@ -341,6 +342,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
* Handles route snapping, deviation detection for rerouting, and map updates.
|
||||
*/
|
||||
fun updateLocation(location: Location) {
|
||||
if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION ) {
|
||||
surfaceRenderer.updateCarSpeed(location.speed)
|
||||
}
|
||||
updateBearing(location)
|
||||
if (routeModel.isNavigating()) {
|
||||
handleNavigationLocation(location)
|
||||
@@ -364,7 +368,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
*/
|
||||
private fun handleNavigationLocation(location: Location) {
|
||||
if (guidanceAudio == 1) {
|
||||
handleGuidanceAudio()
|
||||
handleGuidanceAudio()
|
||||
}
|
||||
navigationScreen.updateTrip(location)
|
||||
if (routeModel.navState.arrived) return
|
||||
@@ -374,9 +378,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
distance > MAXIMAL_ROUTE_DEVIATION -> {
|
||||
navigationScreen.calculateNewRoute(routeModel.navState.destination)
|
||||
}
|
||||
|
||||
distance < MAXIMAL_SNAP_CORRECTION -> {
|
||||
surfaceRenderer.updateLocation(snappedLocation)
|
||||
}
|
||||
|
||||
else -> {
|
||||
surfaceRenderer.updateLocation(location)
|
||||
}
|
||||
@@ -390,6 +396,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
override fun stopNavigation() {
|
||||
routeModel.stopNavigation()
|
||||
navigationManager.navigationEnded()
|
||||
if (autoDriveEnabled) {
|
||||
simulation.stopSimulation()
|
||||
autoDriveEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,6 +408,13 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
*/
|
||||
override fun startNavigation() {
|
||||
navigationManager.navigationStarted()
|
||||
if (autoDriveEnabled) {
|
||||
simulation.startSimulation(
|
||||
routeModel, lifecycle.coroutineScope
|
||||
) { location ->
|
||||
updateLocation(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateTrip(trip: Trip) {
|
||||
@@ -407,7 +424,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
/**
|
||||
* Handle guidance audio
|
||||
* Called when user wants to hear the step by step instructions
|
||||
* Called when user wants to hear the step-by-step instructions
|
||||
*/
|
||||
private fun handleGuidanceAudio() {
|
||||
val currentStep = routeModel.route.currentStep()
|
||||
|
||||
@@ -172,7 +172,7 @@ class RouteCarModel : RouteModel() {
|
||||
}
|
||||
|
||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
|
||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
||||
carContext.getCarService(AppManager::class.java)
|
||||
.showAlert(
|
||||
createAlert(
|
||||
carContext,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.kouros.navigation.car.navigation
|
||||
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.SystemClock
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Simulation {
|
||||
|
||||
private var simulationJob: Job? = null
|
||||
|
||||
fun startSimulation(
|
||||
routeModel: RouteCarModel,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
updateLocation: (Location) -> Unit
|
||||
) {
|
||||
val points = routeModel.curRoute.waypoints
|
||||
if (points.isEmpty()) return
|
||||
simulationJob?.cancel()
|
||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||
var curBearing = 0f
|
||||
simulationJob = lifecycleScope.launch {
|
||||
for (point in points) {
|
||||
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||
latitude = point[1]
|
||||
longitude = point[0]
|
||||
bearing = curBearing
|
||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||
speed = 13.0f // ~50 km/h
|
||||
time = System.currentTimeMillis()
|
||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
}
|
||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
||||
// Update your app's state as if a real GPS update occurred
|
||||
updateLocation(fakeLocation)
|
||||
// Wait before moving to the next point (e.g., every 2 seconds)
|
||||
delay(500)
|
||||
lastLocation = fakeLocation
|
||||
}
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSimulation() {
|
||||
simulationJob?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,12 @@ import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||
import com.kouros.navigation.car.screen.settings.SettingsScreen
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.model.SettingsViewModel
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
import com.kouros.navigation.utils.GeoUtils
|
||||
import com.kouros.navigation.utils.formattedDistance
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
@@ -89,13 +92,16 @@ class NavigationScreen(
|
||||
|
||||
val repository = getSettingsRepository(carContext)
|
||||
|
||||
val settingsViewModel = getSettingsViewModel(carContext)
|
||||
|
||||
|
||||
var distanceMode = 0
|
||||
|
||||
init {
|
||||
observerManager.attachAllObservers(this)
|
||||
lifecycleScope.launch {
|
||||
getSettingsViewModel(carContext).routingEngine.first()
|
||||
getSettingsViewModel(carContext).recentPlaces.first()
|
||||
settingsViewModel.routingEngine.first()
|
||||
settingsViewModel.recentPlaces.first()
|
||||
distanceMode = repository.distanceModeFlow.first()
|
||||
}
|
||||
}
|
||||
@@ -111,7 +117,7 @@ class NavigationScreen(
|
||||
navigationType = NavigationType.NAVIGATION
|
||||
routeModel.startNavigation(route)
|
||||
if (routeModel.hasLegs()) {
|
||||
getSettingsViewModel(carContext).onLastRouteChanged(route)
|
||||
settingsViewModel.onLastRouteChanged(route)
|
||||
}
|
||||
surfaceRenderer.setRouteData()
|
||||
listener.startNavigation()
|
||||
@@ -616,30 +622,37 @@ class NavigationScreen(
|
||||
fun updateTrip(location: Location) {
|
||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val duration = Duration.between(current, lastTrafficDate)
|
||||
if (duration.abs().seconds > 360) {
|
||||
if (duration.abs().seconds > TRAFFIC_UPDATE) {
|
||||
lastTrafficDate = current
|
||||
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||
}
|
||||
updateSpeedCamera(location)
|
||||
with(routeModel) {
|
||||
updateLocation( location, navigationViewModel)
|
||||
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
|
||||
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||
) {
|
||||
stopNavigation()
|
||||
getSettingsViewModel(carContext).onLastRouteChanged("")
|
||||
navState = navState.copy(arrived = true)
|
||||
surfaceRenderer.routeData.value = ""
|
||||
navigationType = NavigationType.ARRIVAL
|
||||
invalidate()
|
||||
}
|
||||
checkArrival()
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for arrival
|
||||
*/
|
||||
private fun RouteCarModel.checkArrival() {
|
||||
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
|
||||
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||
) {
|
||||
listener.stopNavigation()
|
||||
settingsViewModel.onLastRouteChanged("")
|
||||
navState = navState.copy(arrived = true)
|
||||
surfaceRenderer.routeData.value = ""
|
||||
navigationType = NavigationType.ARRIVAL
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the trip information and notifies the listener with a new Trip object.
|
||||
* This includes destination name, address, travel estimate, and loading status.
|
||||
|
||||
Reference in New Issue
Block a user