diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 18fefb2..8210e1e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -45,6 +45,12 @@
android:foregroundServiceType="location"
android:exported="true">
+
+
\ No newline at end of file
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 6c5ebff..0998a1e 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,6 +1,5 @@
package com.kouros.navigation.car
-import android.Manifest
import android.Manifest.permission
import android.content.Intent
import android.content.pm.PackageManager
@@ -54,8 +53,6 @@ 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
@@ -68,7 +65,6 @@ import kotlinx.coroutines.launch
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneOffset
-import kotlin.collections.listOf
import kotlin.math.absoluteValue
@@ -86,7 +82,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
// Model for managing route state and navigation logic for Android Auto
lateinit var routeModel: RouteCarModel
- var route = ""
+ var route = ""
// Main navigation screen displayed to the user
lateinit var navigationScreen: NavigationScreen
@@ -126,12 +122,59 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
var notificationActive = 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?
+ ) {
+ TODO("Not yet implemented")
+ }
+ }
+
+ // Monitors the state of the connection to the Navigation service.
+ val serviceConnection: ServiceConnection = object : ServiceConnection {
+ @RequiresPermission(allOf = [permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION])
+ 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 onPause(owner: LifecycleOwner) {
Log.d(TAG, "NavigationSession paused")
super.onPause(owner)
@@ -142,6 +185,12 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
super.onResume(owner)
}
+ 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()
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..14e05de
--- /dev/null
+++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationService.kt
@@ -0,0 +1,234 @@
+package com.kouros.navigation.car.navigation
+
+import android.Manifest
+import android.app.NotificationChannel
+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.Build
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.RequiresPermission
+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.core.location.LocationListenerCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.kouros.navigation.car.DeviceLocationManager
+import com.kouros.navigation.car.DeviceLocationManagerService
+import com.kouros.navigation.car.NavigationNotificationService
+import com.kouros.navigation.data.Constants.TAG
+
+
+class NavigationService : Service() {
+
+ val TAG: String = "NavigationService"
+
+ 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()
+
+ // Manages device GPS location updates
+ lateinit var deviceLocationManager: DeviceLocationManagerService
+
+ private lateinit var navigationManager: NavigationManager
+ private var navigationManagerInitialized = false
+ var binder: IBinder = LocalBinder()
+
+
+ /** 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 onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ return START_STICKY
+ }
+
+
+ override fun onCreate() {
+ Log.i(TAG, "In onCreate()");
+ createNotificationChannel();
+ }
+
+ override fun onBind(p0: Intent?): IBinder {
+ return binder
+ }
+
+ override fun onUnbind(intent: Intent): Boolean {
+ if (::deviceLocationManager.isInitialized) {
+ deviceLocationManager.stopLocationUpdates()
+ }
+ return true
+ }
+
+ override fun onDestroy() {
+ Log.i(TAG, "In onDestroy()");
+
+ }
+
+ private fun createNotificationChannel() {
+ val serviceChannel = NotificationChannel(
+ "CHANNEL_ID",
+ "Location Service Channel",
+ NotificationManager.IMPORTANCE_HIGH
+ )
+ val manager = getSystemService(NotificationManager::class.java)
+ manager.createNotificationChannel(serviceChannel)
+ }
+
+
+
+
+ /** Sets the [CarContext] to use while the service is connected. */
+ @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
+ fun setCarContext(
+ carContext: CarContext,
+ listener: Listener
+ ) {
+ Log.d(TAG, "in setCarContext")
+ this.carContext = carContext
+
+ deviceLocationManager = DeviceLocationManagerService(
+ carContext = carContext,
+ onLocationUpdate = ::updateLocation,
+ onInitialLocation = { location ->
+ Log.d(TAG, "Initial location: $location")
+ }
+ )
+ deviceLocationManager.startLocationUpdates()
+
+ 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() {
+ Log.i(TAG, "clearContext");
+ carContext = null;
+ deviceLocationManager.stopLocationUpdates()
+ // navigationManager.clearNavigationManagerCallback();
+ }
+
+ /** Starts navigation. */
+ fun startNavigation() {
+ Log.i(TAG, "Starting Navigation")
+ startService(Intent(applicationContext, NavigationService::class.java))
+ Log.i(TAG, "Starting foreground service")
+
+ 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()
+ }
+
+ fun updateLocation(location: Location) {
+ Log.d(TAG, "Update location: $location")
+ }
+
+ // 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)
+ // }
+}