NavigationService
This commit is contained in:
@@ -45,6 +45,12 @@
|
|||||||
android:foregroundServiceType="location"
|
android:foregroundServiceType="location"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".car.navigation.NavigationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:exported="true">
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
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.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.model.RouteModel
|
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
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||||
@@ -68,7 +65,6 @@ import kotlinx.coroutines.launch
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import kotlin.collections.listOf
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +82,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
// Model for managing route state and navigation logic for Android Auto
|
// Model for managing route state and navigation logic for Android Auto
|
||||||
lateinit var routeModel: RouteCarModel
|
lateinit var routeModel: RouteCarModel
|
||||||
|
|
||||||
var route = ""
|
var route = ""
|
||||||
|
|
||||||
// Main navigation screen displayed to the user
|
// Main navigation screen displayed to the user
|
||||||
lateinit var navigationScreen: NavigationScreen
|
lateinit var navigationScreen: NavigationScreen
|
||||||
@@ -126,12 +122,59 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
|
|
||||||
var notificationActive = false
|
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.
|
* Lifecycle observer for managing session lifecycle events.
|
||||||
* Cleans up resources when the session is destroyed.
|
* Cleans up resources when the session is destroyed.
|
||||||
*/
|
*/
|
||||||
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
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) {
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
Log.d(TAG, "NavigationSession paused")
|
Log.d(TAG, "NavigationSession paused")
|
||||||
super.onPause(owner)
|
super.onPause(owner)
|
||||||
@@ -142,6 +185,12 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
super.onResume(owner)
|
super.onResume(owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
Log.i(TAG, "In onStop()")
|
||||||
|
carContext.unbindService(serviceConnection)
|
||||||
|
navigationService = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
if (::navigationManager.isInitialized) {
|
if (::navigationManager.isInitialized) {
|
||||||
navigationManager.clearNavigationManagerCallback()
|
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)
|
||||||
|
// }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user