diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9d3b54c..75f3814 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,10 @@
+
+
+
+
@@ -36,6 +40,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt
index 8e91eae..e64a985 100644
--- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt
+++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt
@@ -1,11 +1,13 @@
package com.kouros.navigation.ui
import android.Manifest
-import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
import android.location.LocationManager
import android.os.Bundle
-import android.os.Process
-import android.widget.Toast
+import android.os.IBinder
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -41,19 +43,16 @@ import com.google.android.gms.location.LocationServices
import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.car.TextToSpeechManager
+import com.kouros.navigation.car.navigation.NavigationService
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
+import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Constants.TILT
-import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.BaseStyleModel
-import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.SimulationType
-import com.kouros.navigation.model.simulate
import com.kouros.navigation.model.simulationJob
-import com.kouros.navigation.model.test
-import com.kouros.navigation.model.testSingle
import com.kouros.navigation.ui.app.AppViewModel
import com.kouros.navigation.ui.app.appViewModel
import com.kouros.navigation.ui.navigation.AppNavGraph
@@ -80,10 +79,13 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
+
+ var navigationService: NavigationService? = null
+
+ var isBound: Boolean = false
val routeData = MutableLiveData("")
val routeModel = RouteModel()
var tilt = TILT
- val useMock = false
val type = SimulationType.SIMULATE
val stepData: MutableLiveData by lazy {
@@ -96,26 +98,21 @@ class MainActivity : ComponentActivity() {
var lastLocation = location(0.0, 0.0)
val observer = Observer { newRoute ->
if (newRoute.isNotEmpty()) {
- val repository = getSettingsRepository(applicationContext)
- val routingEngine = runBlocking { repository.routingEngineFlow.first() }
- routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
- routeModel.startNavigation(newRoute)
- routeData.value = routeModel.curRoute.routeGeoJson
- // checkMock()
+ startNavigation(newRoute)
}
}
+ // Monitors the state of the connection to the navigation service.
+ private val serviceConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ val binder: NavigationService.LocalBinder = service as NavigationService.LocalBinder
+ navigationService = binder.service
+ isBound = true
+ }
- private fun checkMock() {
- if (useMock) {
- when (type) {
- SimulationType.SIMULATE -> simulate(routeModel, mock)
- SimulationType.TEST -> test(applicationContext, routeModel)
-
-
- SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
- else -> {}
- }
+ override fun onServiceDisconnected(name: ComponentName?) {
+ navigationService = null
+ isBound = false
}
}
@@ -127,9 +124,7 @@ class MainActivity : ComponentActivity() {
private lateinit var locationManager: LocationManager
private lateinit var fusedLocationClient: FusedLocationProviderClient
- private lateinit var mock: MockLocation
private var loadRecentPlaces = false
-
lateinit var textToSpeechManager: TextToSpeechManager
var guidanceAudio = 0
@@ -152,20 +147,10 @@ class MainActivity : ComponentActivity() {
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
guidanceAudio = it
})
-
- if (useMock) {
- checkMockLocationEnabled()
- }
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.lastLocation.addOnSuccessListener { _: android.location.Location? ->
navigationViewModel.route.observe(this, observer)
- if (useMock) {
- mock = MockLocation(locationManager)
- mock.setMockLocation(
- homeVogelhart.latitude, homeVogelhart.longitude, 0F
- )
- }
}
lifecycleScope.launch {
getSettingsViewModel(applicationContext).routingEngine.first()
@@ -183,6 +168,27 @@ class MainActivity : ComponentActivity() {
}
}
+ override fun onStart() {
+ super.onStart()
+ Log.i(TAG, "In onStart()")
+ bindService(
+ Intent(this, NavigationService::class.java),
+ serviceConnection,
+ BIND_AUTO_CREATE
+ )
+ requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1)
+ }
+
+ override fun onStop() {
+ Log.i(TAG, "In onStop(). bound $isBound")
+ if (isBound) {
+ unbindService(serviceConnection)
+ isBound = false
+ navigationService = null
+ }
+ super.onStop()
+ }
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StartScreen(
@@ -206,10 +212,8 @@ class MainActivity : ComponentActivity() {
// navigationViewModel.route.value = lastRoute
//}
val userLocationState = rememberUserLocationState(locationProvider)
- if (!useMock) {
val locationState = locationProvider.location.collectAsState()
updateLocation(locationState.value)
- }
val step: StepData? by stepData.observeAsState()
val nextStep: StepData? by nextStepData.observeAsState()
@@ -284,7 +288,7 @@ class MainActivity : ComponentActivity() {
step,
nextStep,
{ stopNavigation {} },
- { simulateNavigation() })
+ { })
}
}
}
@@ -346,18 +350,21 @@ class MainActivity : ComponentActivity() {
}
}
+ fun startNavigation(newRoute: String) {
+ val repository = getSettingsRepository(applicationContext)
+ val routingEngine = runBlocking { repository.routingEngineFlow.first() }
+ routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
+ routeModel.startNavigation(newRoute)
+ routeData.value = routeModel.curRoute.routeGeoJson
+ navigationService?.startNavigation()
+ }
fun stopNavigation(closeSheet: () -> Unit) {
- val latitude = routeModel.curRoute.waypoints[0][1]
- val longitude = routeModel.curRoute.waypoints[0][0]
closeSheet()
routeModel.stopNavigation()
getSettingsViewModel(applicationContext).onLastRouteChanged("")
- if (useMock) {
- simulationJob?.cancel()
- mock.setMockLocation(latitude, longitude, 0F)
- }
routeData.value = ""
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
+ navigationService?.stopNavigation()
}
fun textToSpeech() {
@@ -368,37 +375,5 @@ class MainActivity : ComponentActivity() {
lastStepIndex = currentStep.index
}
}
-
- fun simulateNavigation() {
- simulate(
- routeModel = routeModel, mock = mock
- )
- }
-
- private fun checkMockLocationEnabled() {
- try {
- // Check if mock location is enabled for this app
- val appOpsManager = getSystemService(APP_OPS_SERVICE) as AppOpsManager
- val mode = appOpsManager.checkOp(
- AppOpsManager.OPSTR_MOCK_LOCATION, Process.myUid(), packageName
- )
-
- if (mode != AppOpsManager.MODE_ALLOWED) {
- Toast.makeText(
- this,
- "Please select this app as mock location app in Developer Options",
- Toast.LENGTH_LONG
- ).show()
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
-
- enum class ExpandedType {
- HALF, FULL, COLLAPSED
- }
-
}
diff --git a/automotive/src/main/AndroidManifest.xml b/automotive/src/main/AndroidManifest.xml
index cf7c1e5..c5e7047 100644
--- a/automotive/src/main/AndroidManifest.xml
+++ b/automotive/src/main/AndroidManifest.xml
@@ -70,7 +70,12 @@
android:name="distractionOptimized"
android:value="true" />
-
+
+
\ No newline at end of file
diff --git a/common/car/build.gradle.kts b/common/car/build.gradle.kts
index b064eb1..29b90ca 100644
--- a/common/car/build.gradle.kts
+++ b/common/car/build.gradle.kts
@@ -29,7 +29,6 @@ android {
}
buildFeatures {
compose = true
- buildConfig = true
}
}
diff --git a/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt b/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt
index f7a6adf..3b56910 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt
@@ -137,7 +137,7 @@ class CarSensorManager(
carCompassListener
)
carSensors.addCarHardwareLocationListener(
- CarSensors.UPDATE_RATE_NORMAL,
+ CarSensors.UPDATE_RATE_FASTEST,
carContext.mainExecutor,
carLocationListener
)
diff --git a/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt b/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt
new file mode 100644
index 0000000..a837570
--- /dev/null
+++ b/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.kouros.navigation.car
+
+import android.content.Intent
+import android.content.res.Configuration
+import android.util.Log
+import androidx.car.app.CarContext
+import androidx.car.app.CarToast
+import androidx.car.app.Screen
+import androidx.car.app.Session
+import androidx.car.app.model.Action
+import androidx.car.app.model.CarIcon
+import androidx.car.app.model.OnClickListener
+import androidx.car.app.navigation.model.Trip
+import androidx.core.graphics.drawable.IconCompat
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.lifecycleScope
+import com.kouros.data.R
+import com.kouros.navigation.car.navigation.NavigationService
+import com.kouros.navigation.car.navigation.RouteCarModel
+import com.kouros.navigation.car.screen.NavigationListener
+import com.kouros.navigation.car.screen.NavigationScreen
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+
+/** Session class for the Navigation sample app. */
+internal class ClusterSession : Session(), NavigationListener {
+ var mNavigationScreen: NavigationScreen? = null
+
+ var mNavigationCarSurface: SurfaceRenderer? = null
+
+ // A reference to the navigation service used to get location updates and routing.
+ var service: NavigationService? = null
+
+ var mSettingsAction: Action? = null
+
+ var routeModel = RouteCarModel()
+
+ lateinit var viewModelStoreOwner: ViewModelStoreOwner
+
+ override fun onCreateScreen(intent: Intent): Screen {
+ Log.i(TAG, "In onCreateScreen()")
+
+ setupViewModelStore()
+ mSettingsAction =
+ Action.Builder()
+ .setIcon(
+ CarIcon.Builder(
+ IconCompat.createWithResource(
+ carContext, R.drawable.alt_route_48px
+ )
+ )
+ .build()
+ )
+ .setOnClickListener(
+ OnClickListener {})
+ .build()
+
+ mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
+
+ // mNavigationScreen =
+// new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface);
+ val action = intent.action
+ if (CarContext.ACTION_NAVIGATE == action) {
+ CarToast.makeText(
+ carContext,
+ "Navigation intent: " + intent.dataString,
+ CarToast.LENGTH_LONG
+ )
+ .show()
+ }
+
+ return mNavigationScreen!!
+ }
+
+ override fun onCarConfigurationChanged(newConfiguration: Configuration) {
+ // mNavigationCarSurface.onCarConfigurationChanged();
+ }
+
+
+ override fun stopNavigation() {
+ if (service != null) {
+ service!!.stopNavigation()
+ }
+ }
+
+ override fun startNavigation() {
+ }
+
+ override fun updateTrip(trip: Trip) {
+ }
+
+ companion object {
+ val TAG: String = ClusterSession::class.java.getSimpleName()
+ }
+
+ private fun setupViewModelStore() {
+ viewModelStoreOwner = object : ViewModelStoreOwner {
+ override val viewModelStore = ViewModelStore()
+ }
+
+ lifecycleScope.launch {
+ try {
+ awaitCancellation()
+ } finally {
+ viewModelStoreOwner.viewModelStore.clear()
+ }
+ }
+ }
+}
diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt
index c103e48..0378d3e 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt
@@ -1,11 +1,15 @@
package com.kouros.navigation.car
import android.annotation.SuppressLint
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.net.Uri
+import android.util.Log
import androidx.car.app.CarAppService
import androidx.car.app.Session
import androidx.car.app.SessionInfo
import androidx.car.app.validation.HostValidator
+import com.kouros.navigation.data.Constants.TAG
class NavigationCarAppService : CarAppService() {
@@ -13,6 +17,7 @@ class NavigationCarAppService : CarAppService() {
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP =
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
+ val channelId: String = "NavigationSessionChannel"
fun createDeepLinkUri(deepLinkAction: String): Uri {
return Uri.fromParts(NavigationSession.uriScheme, NavigationSession.uriHost, deepLinkAction)
@@ -26,7 +31,26 @@ class NavigationCarAppService : CarAppService() {
}
override fun onCreateSession(sessionInfo: SessionInfo): Session {
- return NavigationSession()
+ Log.d(TAG, "Display Type: ${sessionInfo.displayType}")
+ if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
+ return ClusterSession()
+ } else {
+ createNotificationChannel()
+ return NavigationSession()
+ }
+ }
+
+ private fun createNotificationChannel() {
+ val notificationManager =
+ getSystemService(NotificationManager::class.java)
+ val name: CharSequence = "Car App Service"
+ val serviceChannel =
+ NotificationChannel(
+ channelId,
+ name,
+ NotificationManager.IMPORTANCE_HIGH
+ )
+ notificationManager.createNotificationChannel(serviceChannel)
}
}
diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt
index 98950f4..7cb4383 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt
@@ -1,17 +1,27 @@
package com.kouros.navigation.car
import android.Manifest.permission
+import android.content.ComponentName
+import android.content.Context
import android.content.Intent
+import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.location.Location
+import android.os.IBinder
import android.util.Log
import androidx.car.app.CarContext
+import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.ScreenManager
import androidx.car.app.Session
import androidx.car.app.connection.CarConnection
+import androidx.car.app.model.CarIcon
+import androidx.car.app.model.Distance
import androidx.car.app.navigation.NavigationManager
import androidx.car.app.navigation.NavigationManagerCallback
+import androidx.car.app.navigation.model.Destination
+import androidx.car.app.navigation.model.Step
+import androidx.car.app.navigation.model.TravelEstimate
import androidx.car.app.navigation.model.Trip
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver
@@ -22,6 +32,7 @@ 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
@@ -36,7 +47,6 @@ 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.Place
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.data.osrm.OsrmRepository
@@ -47,9 +57,7 @@ import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.getSettingsRepository
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import java.time.LocalDateTime
import java.time.ZoneOffset
@@ -90,11 +98,64 @@ class NavigationSession : Session(), NavigationListener {
var navigationManagerStarted = false
+ var navigationService : NavigationService? = null
+
+
+ val serviceListener : NavigationService.Listener = object : NavigationService.Listener {
+ override fun navigationStateChanged(
+ isNavigating: Boolean,
+ isRerouting: Boolean,
+ hasArrived: Boolean,
+ destinations: MutableList?,
+ steps: MutableList?,
+ nextDestinationTravelEstimate: TravelEstimate?,
+ nextStepRemainingDistance: Distance?,
+ shouldShowNextStep: Boolean,
+ shouldShowLanes: Boolean,
+ junctionImage: CarIcon?
+ ) {
+ //navigationScreen.updateTrip()
+ }
+ }
+
+ // Monitors the state of the connection to the Navigation service.
+ val serviceConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ Log.i(TAG, "In onServiceConnected() Session component:$service")
+ val binder: NavigationService.LocalBinder = service as NavigationService.LocalBinder
+ navigationService = binder.service
+ navigationService!!.setCarContext(carContext, serviceListener)
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ Log.i(TAG, "In onServiceDisconnected() component: $name")
+ // Unhook map models here
+ navigationService!!.clearCarContext()
+ navigationService = null
+ }
+ }
+
/**
* Lifecycle observer for managing session lifecycle events.
* Cleans up resources when the session is destroyed.
*/
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ Log.i(TAG, "In onStart() Session")
+ carContext
+ .bindService(
+ Intent(carContext, NavigationService::class.java),
+ serviceConnection,
+ Context.BIND_AUTO_CREATE
+ )
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ Log.i(TAG, "In onStop()")
+ carContext.unbindService(serviceConnection)
+ navigationService = null
+ }
+
override fun onDestroy(owner: LifecycleOwner) {
if (::navigationManager.isInitialized) {
navigationManager.clearNavigationManagerCallback()
@@ -220,12 +281,9 @@ class NavigationSession : Session(), NavigationListener {
// Called when the app should simulate navigation (e.g., for testing)
deviceLocationManager.stopLocationUpdates()
autoDriveEnabled = true
- surfaceRenderer.viewStyle = ViewStyle.VIEW
- simulation.startSimulation(
- routeModel, lifecycle.coroutineScope
- ) { location ->
- updateLocation(location)
- }
+ startNavigation()
+ CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG)
+ .show()
}
override fun onStopNavigation() {
@@ -427,6 +485,7 @@ class NavigationSession : Session(), NavigationListener {
surfaceRenderer.routeData.value = ""
surfaceRenderer.viewStyle = ViewStyle.VIEW
navigationScreen.navigationType = NavigationType.VIEW
+ navigationService!!.stopNavigation()
}
/**
diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt
index 8f1ecf7..ee8b1eb 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt
@@ -64,7 +64,8 @@ import java.time.LocalDateTime
* Manages camera position, zoom, tilt, and navigation state for the map view.
*/
class SurfaceRenderer(
- private var carContext: CarContext, lifecycle: Lifecycle,
+ private var carContext: CarContext,
+ private var lifecycle: Lifecycle,
private var routeModel: RouteCarModel,
private var viewModelStoreOwner: ViewModelStoreOwner
) : DefaultLifecycleObserver {
diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationService.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationService.kt
new file mode 100644
index 0000000..9027d3f
--- /dev/null
+++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationService.kt
@@ -0,0 +1,258 @@
+package com.kouros.navigation.car.navigation
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.os.Binder
+import android.os.IBinder
+import android.util.Log
+import androidx.car.app.CarContext
+import androidx.car.app.model.CarIcon
+import androidx.car.app.model.Distance
+import androidx.car.app.navigation.NavigationManager
+import androidx.car.app.navigation.model.Destination
+import androidx.car.app.navigation.model.Step
+import androidx.car.app.navigation.model.TravelEstimate
+import androidx.car.app.notification.CarAppExtender
+import androidx.car.app.notification.CarPendingIntent
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.kouros.data.R
+import com.kouros.navigation.car.NavigationCarAppService
+import com.kouros.navigation.data.Constants.TAG
+import androidx.core.graphics.toColorInt
+
+class NavigationService : Service() {
+
+
+ val DEEP_LINK_ACTION: String = ("com.kouros.navigation.car.navigation"
+ + ".NavigationDeepLinkAction")
+
+ val channelId : String = "NavigationServiceChannel"
+
+ /** The identifier for the navigation notification displayed for the foreground service. */
+
+ val NAV_NOTIFICATION_ID: Int = 87356325
+
+ /** The identifier for the non-navigation notifications, such as a traffic accident warning. */
+
+ val NOTIFICATION_ID: Int = 71653346
+
+ // Constants for location broadcast
+ val PACKAGE_NAME: String =
+ "androidx.car.app.sample.navigation.common.nav.navigationservice"
+
+ val EXTRA_STARTED_FROM_NOTIFICATION: String = PACKAGE_NAME + ".started_from_notification"
+
+ val CANCEL_ACTION: String = "CANCEL"
+
+ private var notificationManager: NotificationManager? = null
+ private var carContext: CarContext? = null
+
+ private lateinit var listener: Listener
+
+ // Model for managing route state and navigation logic for Android Auto
+ var routeModel = RouteCarModel()
+
+ private lateinit var navigationManager: NavigationManager
+ private var navigationManagerInitialized = false
+ var binder: IBinder = LocalBinder()
+
+ override fun onBind(p0: Intent?): IBinder {
+ return binder
+ }
+
+ override fun onUnbind(intent: Intent): Boolean {
+ return true
+ }
+
+ /** A listener for the navigation state changes. */
+ interface Listener {
+ /** Callback called when the navigation state changes. */
+ fun navigationStateChanged(
+ isNavigating: Boolean,
+ isRerouting: Boolean,
+ hasArrived: Boolean,
+ destinations: MutableList?,
+ steps: MutableList?,
+ nextDestinationTravelEstimate: TravelEstimate?,
+ nextStepRemainingDistance: Distance?,
+ shouldShowNextStep: Boolean,
+ shouldShowLanes: Boolean,
+ junctionImage: CarIcon?
+ )
+ }
+
+ /**
+ * Class used for the client Binder. Since this service runs in the same process as its clients,
+ * we don't need to deal with IPC.
+ */
+ inner class LocalBinder : Binder() {
+ val service: NavigationService
+ get() = this@NavigationService
+ }
+
+ override fun onCreate() {
+ Log.i(TAG, "In onCreate()");
+ createNotificationChannel();
+ }
+
+ /** Sets the [CarContext] to use while the service is connected. */
+ fun setCarContext(
+ carContext: CarContext,
+ listener: Listener
+ ) {
+ Log.d(TAG, "in setCarContext")
+ this.carContext = carContext
+ navigationManagerInitialized = true
+// navigationManager =
+// carContext.getCarService(NavigationManager::class.java)
+// navigationManager.setNavigationManagerCallback(
+// object : NavigationManagerCallback {
+// override fun onStopNavigation() {
+// this@NavigationService.stopNavigation()
+// }
+//
+// override fun onAutoDriveEnabled() {
+// Log.d(TAG, "onAutoDriveEnabled called")
+// CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG)
+// .show()
+// }
+// })
+ this.listener = listener
+
+ // Uncomment if navigating
+ // mNavigationManager.navigationStarted();
+ }
+
+ /** Clears the currently used {@link CarContext}. */
+ fun clearCarContext() {
+ carContext = null;
+ // navigationManager.clearNavigationManagerCallback();
+ }
+
+ /** Starts navigation. */
+ fun startNavigation() {
+ Log.i(TAG, "Starting Navigation")
+ startService(Intent(applicationContext, NavigationService::class.java))
+ Log.i(TAG, "Starting foreground service")
+ startForeground(
+ NAV_NOTIFICATION_ID,
+ getNotification(
+ true,
+ showInCar = false,
+ navigatingDisplayTitle = getString(R.string.navigation_settings),
+ navigatingDisplayContent = null,
+ notificationIcon = R.drawable.navigation_48px
+ )
+ )
+
+ listener.navigationStateChanged(
+ isNavigating = false,
+ isRerouting = true,
+ hasArrived = false,
+ destinations = null,
+ steps = null,
+ nextDestinationTravelEstimate = null,
+ nextStepRemainingDistance = null,
+ shouldShowNextStep = false,
+ shouldShowLanes = false,
+ junctionImage = null
+ )
+
+ }
+
+ /** Starts navigation. */
+ fun stopNavigation() {
+ // if (navigationManagerInitialized)
+// navigationManager.navigationEnded()
+ listener.navigationStateChanged(
+ false,
+ isRerouting = false,
+ hasArrived = false,
+ destinations = null,
+ steps = null,
+ nextDestinationTravelEstimate = null,
+ nextStepRemainingDistance = null,
+ shouldShowNextStep = false,
+ shouldShowLanes = false,
+ junctionImage = null,
+ )
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ stopSelf()
+ }
+
+ private fun createNotificationChannel() {
+ notificationManager =
+ getSystemService(NotificationManager::class.java)
+ val name: CharSequence = getString(R.string.navigation_settings)
+ val serviceChannel =
+ NotificationChannel(
+ channelId,
+ name,
+ NotificationManager.IMPORTANCE_HIGH
+ )
+ notificationManager!!.createNotificationChannel(serviceChannel)
+ }
+
+ /** Returns the [NotificationCompat] used as part of the foreground service. */
+ private fun getNotification(
+ shouldNotify: Boolean,
+ showInCar: Boolean,
+ navigatingDisplayTitle: CharSequence?,
+ navigatingDisplayContent: CharSequence?,
+ notificationIcon: Int
+ ): Notification {
+ val builder: NotificationCompat.Builder =
+ NotificationCompat.Builder(this, channelId)
+ //.setContentIntent(createMainActivityPendingIntent())
+ .setContentTitle(navigatingDisplayTitle)
+ .setContentText(navigatingDisplayContent)
+ .setOngoing(true)
+ .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
+ .setOnlyAlertOnce(!shouldNotify) // Set the notification's background color on the car screen.
+
+ .setColor(
+ "#003000".toColorInt()
+ )
+ .setColorized(true)
+ .setSmallIcon(R.drawable.ic_pan_24)
+ .setLargeIcon(
+ BitmapFactory.decodeResource(resources, notificationIcon)
+ )
+ .setTicker(navigatingDisplayTitle)
+ .setWhen(System.currentTimeMillis())
+
+ builder.setChannelId(channelId)
+ builder.setPriority(NotificationManager.IMPORTANCE_HIGH)
+ if (showInCar) {
+ val intent = Intent(Intent.ACTION_VIEW)
+ .setComponent(ComponentName(this, NavigationCarAppService::class.java))
+ .setData(NavigationCarAppService().createDeepLinkUri(Intent.ACTION_VIEW))
+ builder.extend(
+ CarAppExtender.Builder()
+ .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
+ .setContentIntent(
+ CarPendingIntent.getCarApp(
+ this, intent.hashCode(),
+ intent,
+ 0
+ )
+ )
+ .build()
+ )
+ }
+ return builder.build()
+ }
+
+// private fun createMainActivityPendingIntent(): PendingIntent? {
+// val intent: Intent = Intent(this, MainActivity::class.java)
+// intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true)
+// return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+// }
+}
diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt
index 9e8cd12..18aba18 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt
@@ -3,7 +3,6 @@ package com.kouros.navigation.car.navigation
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
-import android.util.Log
import androidx.annotation.StringRes
import androidx.car.app.AppManager
import androidx.car.app.CarContext
@@ -92,8 +91,17 @@ class RouteCarModel : RouteModel() {
return step
}
- fun travelEstimate(carContext: CarContext, distanceMode: Int): TravelEstimate {
- val timeLeft = routeCalculator.travelLeftTime()
+ fun travelEstimateTrip(carContext: CarContext, distanceMode: Int): TravelEstimate {
+
+ return travelEstimate(carContext, routeCalculator.travelLeftTime(), distanceMode)
+ }
+
+ fun travelEstimateStep(carContext: CarContext, distanceMode: Int): TravelEstimate {
+ return travelEstimate(carContext, routeCalculator.travelStepLeftTime(), distanceMode)
+ }
+
+ fun travelEstimate(carContext: CarContext, timeLeft: Double, distanceMode: Int): TravelEstimate {
+
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance())
diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt
index c4e18b1..e0ce33e 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt
@@ -4,7 +4,7 @@ import android.location.Location
import android.location.LocationManager
import android.os.SystemClock
import androidx.lifecycle.LifecycleCoroutineScope
-import com.kouros.android.cars.carappservice.BuildConfig
+import com.kouros.data.BuildConfig
import com.kouros.navigation.data.tomtom.TomTomRepository
import io.ticofab.androidgpxparser.parser.GPXParser
import io.ticofab.androidgpxparser.parser.domain.Gpx
diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt
index 9372964..0864432 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt
@@ -169,7 +169,7 @@ open class NavigationScreen(
.setNavigationInfo(
getRoutingInfo()
)
- .setDestinationTravelEstimate(routeModel.travelEstimate(carContext, distanceMode))
+ .setDestinationTravelEstimate(routeModel.travelEstimateTrip(carContext, distanceMode))
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(
mapActionStrip(
@@ -627,9 +627,11 @@ open class NavigationScreen(
.build()
tripBuilder.addDestination(
destination,
- routeModel.travelEstimate(carContext, distanceMode)
+ routeModel.travelEstimateTrip(carContext, distanceMode)
)
tripBuilder.setLoading(false)
+ tripBuilder.setCurrentRoad(routeModel.currentStep.street)
+ tripBuilder.addStep(routeModel.currentStep(carContext), routeModel.travelEstimateStep(carContext, distanceMode ))
listener.updateTrip(tripBuilder.build())
}
}
diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt
index 51a8aa3..912a9be 100644
--- a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt
+++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt
@@ -3,9 +3,9 @@ package com.kouros.navigation.car.screen
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
-import android.util.Log
import androidx.car.app.CarContext
import androidx.car.app.CarToast
+import androidx.car.app.OnScreenResultListener
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts
index 6d81d16..4a7fc2d 100644
--- a/common/data/build.gradle.kts
+++ b/common/data/build.gradle.kts
@@ -18,6 +18,7 @@ android {
buildFeatures {
compose = true
+ buildConfig = true
}
buildTypes {
diff --git a/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt b/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt
index d86e805..8c78128 100644
--- a/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt
+++ b/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt
@@ -10,15 +10,24 @@ data class NavigationState (
val iconMapper: IconMapper = IconMapper(),
val navigating: Boolean = false,
val arrived: Boolean = false,
+ // travel message
val travelMessage: String = "",
+ // maneuver type
val maneuverType: Int = 0,
+ // last location
val lastLocation: Location = location(0.0, 0.0),
+ // current location
val currentLocation: Location = location(0.0, 0.0),
+ // bearing of the route
val routeBearing: Float = 0F,
// index of current route in the list of routes
val currentRouteIndex: Int = 0,
+ // destination name
val destination: Place = Place(),
+ // car connection used
val carConnection: Int = 0,
+ // routing engine used
val routingEngine: Int = 0,
+ // show next step information
val nextStep: Boolean = false,
)
diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt
index 7c5f5e8..56b0c43 100644
--- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt
+++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt
@@ -2,6 +2,7 @@ package com.kouros.navigation.data.tomtom
import android.content.Context
import android.location.Location
+import com.kouros.data.BuildConfig
import com.kouros.data.R
import com.kouros.navigation.data.EngineType
import com.kouros.navigation.data.NavigationRepository
@@ -20,9 +21,9 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
private const val tomtomFields =
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
-const val useLocal = false
+val useLocal = BuildConfig.DEBUG
-const val useLocalTraffic = false
+val useLocalTraffic = BuildConfig.DEBUG
class TomTomRepository : NavigationRepository() {
diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt
index c5eb723..defe253 100644
--- a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt
+++ b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt
@@ -54,6 +54,18 @@ class RouteCalculator(var routeModel: RouteModel) {
return timeLeft
}
+ fun travelStepLeftTime(): Double {
+ var timeLeft = 0.0
+ // time for current step
+ val step = routeModel.route.currentStep()
+ val curTime = step.duration
+ val percent =
+ 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
+ val time = curTime * percent / 100
+ timeLeft += time
+ return timeLeft
+ }
+
/** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double {
val step = routeModel.route.currentStep()