NavigationService

This commit is contained in:
Dimitris
2026-04-03 09:57:52 +02:00
parent 24173412e8
commit 757c4c8d8d
3 changed files with 294 additions and 5 deletions
+6
View File
@@ -45,6 +45,12 @@
android:foregroundServiceType="location"
android:exported="true">
</service>
<service
android:name=".car.navigation.NavigationService"
android:enabled="true"
android:foregroundServiceType="location"
android:exported="true">
</service>
</application>
</manifest>
@@ -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
@@ -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<Destination?>?,
steps: MutableList<Step>?,
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()
@@ -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<Destination?>?,
steps: MutableList<Step>?,
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)
// }
}