Cluster
This commit is contained in:
@@ -6,6 +6,10 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
|
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
|
||||||
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
||||||
tools:ignore="MockLocation" />
|
tools:ignore="MockLocation" />
|
||||||
@@ -36,6 +40,12 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name="com.kouros.navigation.car.navigation.NavigationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:exported="true">
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
import android.Manifest
|
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.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Process
|
import android.os.IBinder
|
||||||
import android.widget.Toast
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
@@ -41,19 +43,16 @@ import com.google.android.gms.location.LocationServices
|
|||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||||
import com.kouros.navigation.car.TextToSpeechManager
|
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.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.INSTRUCTION_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.TILT
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.model.MockLocation
|
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.model.SimulationType
|
import com.kouros.navigation.model.SimulationType
|
||||||
import com.kouros.navigation.model.simulate
|
|
||||||
import com.kouros.navigation.model.simulationJob
|
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.app.appViewModel
|
import com.kouros.navigation.ui.app.appViewModel
|
||||||
import com.kouros.navigation.ui.navigation.AppNavGraph
|
import com.kouros.navigation.ui.navigation.AppNavGraph
|
||||||
@@ -80,10 +79,13 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
var navigationService: NavigationService? = null
|
||||||
|
|
||||||
|
var isBound: Boolean = false
|
||||||
val routeData = MutableLiveData("")
|
val routeData = MutableLiveData("")
|
||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
var tilt = TILT
|
var tilt = TILT
|
||||||
val useMock = false
|
|
||||||
|
|
||||||
val type = SimulationType.SIMULATE
|
val type = SimulationType.SIMULATE
|
||||||
val stepData: MutableLiveData<StepData> by lazy {
|
val stepData: MutableLiveData<StepData> by lazy {
|
||||||
@@ -96,26 +98,21 @@ class MainActivity : ComponentActivity() {
|
|||||||
var lastLocation = location(0.0, 0.0)
|
var lastLocation = location(0.0, 0.0)
|
||||||
val observer = Observer<String> { newRoute ->
|
val observer = Observer<String> { newRoute ->
|
||||||
if (newRoute.isNotEmpty()) {
|
if (newRoute.isNotEmpty()) {
|
||||||
val repository = getSettingsRepository(applicationContext)
|
startNavigation(newRoute)
|
||||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
|
||||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
|
||||||
routeModel.startNavigation(newRoute)
|
|
||||||
routeData.value = routeModel.curRoute.routeGeoJson
|
|
||||||
// checkMock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Monitors the state of the connection to the navigation service.
|
||||||
private fun checkMock() {
|
private val serviceConnection: ServiceConnection = object : ServiceConnection {
|
||||||
if (useMock) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
when (type) {
|
val binder: NavigationService.LocalBinder = service as NavigationService.LocalBinder
|
||||||
SimulationType.SIMULATE -> simulate(routeModel, mock)
|
navigationService = binder.service
|
||||||
SimulationType.TEST -> test(applicationContext, routeModel)
|
isBound = true
|
||||||
|
|
||||||
|
|
||||||
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 locationManager: LocationManager
|
||||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||||
private lateinit var mock: MockLocation
|
|
||||||
private var loadRecentPlaces = false
|
private var loadRecentPlaces = false
|
||||||
|
|
||||||
lateinit var textToSpeechManager: TextToSpeechManager
|
lateinit var textToSpeechManager: TextToSpeechManager
|
||||||
|
|
||||||
var guidanceAudio = 0
|
var guidanceAudio = 0
|
||||||
@@ -152,20 +147,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
||||||
guidanceAudio = it
|
guidanceAudio = it
|
||||||
})
|
})
|
||||||
|
|
||||||
if (useMock) {
|
|
||||||
checkMockLocationEnabled()
|
|
||||||
}
|
|
||||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||||
fusedLocationClient.lastLocation.addOnSuccessListener { _: android.location.Location? ->
|
fusedLocationClient.lastLocation.addOnSuccessListener { _: android.location.Location? ->
|
||||||
navigationViewModel.route.observe(this, observer)
|
navigationViewModel.route.observe(this, observer)
|
||||||
if (useMock) {
|
|
||||||
mock = MockLocation(locationManager)
|
|
||||||
mock.setMockLocation(
|
|
||||||
homeVogelhart.latitude, homeVogelhart.longitude, 0F
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
getSettingsViewModel(applicationContext).routingEngine.first()
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun StartScreen(
|
fun StartScreen(
|
||||||
@@ -206,10 +212,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
// navigationViewModel.route.value = lastRoute
|
// navigationViewModel.route.value = lastRoute
|
||||||
//}
|
//}
|
||||||
val userLocationState = rememberUserLocationState(locationProvider)
|
val userLocationState = rememberUserLocationState(locationProvider)
|
||||||
if (!useMock) {
|
|
||||||
val locationState = locationProvider.location.collectAsState()
|
val locationState = locationProvider.location.collectAsState()
|
||||||
updateLocation(locationState.value)
|
updateLocation(locationState.value)
|
||||||
}
|
|
||||||
val step: StepData? by stepData.observeAsState()
|
val step: StepData? by stepData.observeAsState()
|
||||||
val nextStep: StepData? by nextStepData.observeAsState()
|
val nextStep: StepData? by nextStepData.observeAsState()
|
||||||
|
|
||||||
@@ -284,7 +288,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
step,
|
step,
|
||||||
nextStep,
|
nextStep,
|
||||||
{ stopNavigation {} },
|
{ 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) {
|
fun stopNavigation(closeSheet: () -> Unit) {
|
||||||
val latitude = routeModel.curRoute.waypoints[0][1]
|
|
||||||
val longitude = routeModel.curRoute.waypoints[0][0]
|
|
||||||
closeSheet()
|
closeSheet()
|
||||||
routeModel.stopNavigation()
|
routeModel.stopNavigation()
|
||||||
getSettingsViewModel(applicationContext).onLastRouteChanged("")
|
getSettingsViewModel(applicationContext).onLastRouteChanged("")
|
||||||
if (useMock) {
|
|
||||||
simulationJob?.cancel()
|
|
||||||
mock.setMockLocation(latitude, longitude, 0F)
|
|
||||||
}
|
|
||||||
routeData.value = ""
|
routeData.value = ""
|
||||||
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
|
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
|
||||||
|
navigationService?.stopNavigation()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun textToSpeech() {
|
fun textToSpeech() {
|
||||||
@@ -368,37 +375,5 @@ class MainActivity : ComponentActivity() {
|
|||||||
lastStepIndex = currentStep.index
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,12 @@
|
|||||||
android:name="distractionOptimized"
|
android:name="distractionOptimized"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name="com.kouros.navigation.car.navigation.NavigationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:exported="true">
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -29,7 +29,6 @@ android {
|
|||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
buildConfig = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class CarSensorManager(
|
|||||||
carCompassListener
|
carCompassListener
|
||||||
)
|
)
|
||||||
carSensors.addCarHardwareLocationListener(
|
carSensors.addCarHardwareLocationListener(
|
||||||
CarSensors.UPDATE_RATE_NORMAL,
|
CarSensors.UPDATE_RATE_FASTEST,
|
||||||
carContext.mainExecutor,
|
carContext.mainExecutor,
|
||||||
carLocationListener
|
carLocationListener
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.car.app.CarAppService
|
import androidx.car.app.CarAppService
|
||||||
import androidx.car.app.Session
|
import androidx.car.app.Session
|
||||||
import androidx.car.app.SessionInfo
|
import androidx.car.app.SessionInfo
|
||||||
import androidx.car.app.validation.HostValidator
|
import androidx.car.app.validation.HostValidator
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
|
|
||||||
|
|
||||||
class NavigationCarAppService : CarAppService() {
|
class NavigationCarAppService : CarAppService() {
|
||||||
@@ -13,6 +17,7 @@ class NavigationCarAppService : CarAppService() {
|
|||||||
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP =
|
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP =
|
||||||
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
||||||
|
|
||||||
|
val channelId: String = "NavigationSessionChannel"
|
||||||
|
|
||||||
fun createDeepLinkUri(deepLinkAction: String): Uri {
|
fun createDeepLinkUri(deepLinkAction: String): Uri {
|
||||||
return Uri.fromParts(NavigationSession.uriScheme, NavigationSession.uriHost, deepLinkAction)
|
return Uri.fromParts(NavigationSession.uriScheme, NavigationSession.uriHost, deepLinkAction)
|
||||||
@@ -26,8 +31,27 @@ class NavigationCarAppService : CarAppService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
||||||
|
Log.d(TAG, "Display Type: ${sessionInfo.displayType}")
|
||||||
|
if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
|
||||||
|
return ClusterSession()
|
||||||
|
} else {
|
||||||
|
createNotificationChannel()
|
||||||
return NavigationSession()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.CarToast
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.ScreenManager
|
import androidx.car.app.ScreenManager
|
||||||
import androidx.car.app.Session
|
import androidx.car.app.Session
|
||||||
import androidx.car.app.connection.CarConnection
|
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.NavigationManager
|
||||||
import androidx.car.app.navigation.NavigationManagerCallback
|
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.car.app.navigation.model.Trip
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
@@ -22,6 +32,7 @@ import androidx.lifecycle.ViewModelStoreOwner
|
|||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.kouros.navigation.car.navigation.NavigationService
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.car.navigation.Simulation
|
import com.kouros.navigation.car.navigation.Simulation
|
||||||
import com.kouros.navigation.car.screen.NavigationListener
|
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_ROUTE_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.Place
|
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.ViewStyle
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
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.NavigationUtils.getViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
@@ -90,11 +98,64 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
|
|
||||||
var navigationManagerStarted = false
|
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<Destination?>?,
|
||||||
|
steps: MutableList<Step>?,
|
||||||
|
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.
|
* 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 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()
|
||||||
@@ -220,12 +281,9 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
// Called when the app should simulate navigation (e.g., for testing)
|
// Called when the app should simulate navigation (e.g., for testing)
|
||||||
deviceLocationManager.stopLocationUpdates()
|
deviceLocationManager.stopLocationUpdates()
|
||||||
autoDriveEnabled = true
|
autoDriveEnabled = true
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
startNavigation()
|
||||||
simulation.startSimulation(
|
CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG)
|
||||||
routeModel, lifecycle.coroutineScope
|
.show()
|
||||||
) { location ->
|
|
||||||
updateLocation(location)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopNavigation() {
|
override fun onStopNavigation() {
|
||||||
@@ -427,6 +485,7 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
surfaceRenderer.routeData.value = ""
|
surfaceRenderer.routeData.value = ""
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
navigationScreen.navigationType = NavigationType.VIEW
|
navigationScreen.navigationType = NavigationType.VIEW
|
||||||
|
navigationService!!.stopNavigation()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ import java.time.LocalDateTime
|
|||||||
* Manages camera position, zoom, tilt, and navigation state for the map view.
|
* Manages camera position, zoom, tilt, and navigation state for the map view.
|
||||||
*/
|
*/
|
||||||
class SurfaceRenderer(
|
class SurfaceRenderer(
|
||||||
private var carContext: CarContext, lifecycle: Lifecycle,
|
private var carContext: CarContext,
|
||||||
|
private var lifecycle: Lifecycle,
|
||||||
private var routeModel: RouteCarModel,
|
private var routeModel: RouteCarModel,
|
||||||
private var viewModelStoreOwner: ViewModelStoreOwner
|
private var viewModelStoreOwner: ViewModelStoreOwner
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|||||||
@@ -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<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 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)
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.kouros.navigation.car.navigation
|
|||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
@@ -92,8 +91,17 @@ class RouteCarModel : RouteModel() {
|
|||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|
||||||
fun travelEstimate(carContext: CarContext, distanceMode: Int): TravelEstimate {
|
fun travelEstimateTrip(carContext: CarContext, distanceMode: Int): TravelEstimate {
|
||||||
val timeLeft = routeCalculator.travelLeftTime()
|
|
||||||
|
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 =
|
val timeToDestinationMillis =
|
||||||
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
||||||
val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance())
|
val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.location.Location
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.kouros.android.cars.carappservice.BuildConfig
|
import com.kouros.data.BuildConfig
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import io.ticofab.androidgpxparser.parser.GPXParser
|
import io.ticofab.androidgpxparser.parser.GPXParser
|
||||||
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ open class NavigationScreen(
|
|||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
getRoutingInfo()
|
getRoutingInfo()
|
||||||
)
|
)
|
||||||
.setDestinationTravelEstimate(routeModel.travelEstimate(carContext, distanceMode))
|
.setDestinationTravelEstimate(routeModel.travelEstimateTrip(carContext, distanceMode))
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(
|
||||||
@@ -627,9 +627,11 @@ open class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
tripBuilder.addDestination(
|
tripBuilder.addDestination(
|
||||||
destination,
|
destination,
|
||||||
routeModel.travelEstimate(carContext, distanceMode)
|
routeModel.travelEstimateTrip(carContext, distanceMode)
|
||||||
)
|
)
|
||||||
tripBuilder.setLoading(false)
|
tripBuilder.setLoading(false)
|
||||||
|
tripBuilder.setCurrentRoad(routeModel.currentStep.street)
|
||||||
|
tripBuilder.addStep(routeModel.currentStep(carContext), routeModel.travelEstimateStep(carContext, distanceMode ))
|
||||||
listener.updateTrip(tripBuilder.build())
|
listener.updateTrip(tripBuilder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package com.kouros.navigation.car.screen
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.util.Log
|
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.CarToast
|
import androidx.car.app.CarToast
|
||||||
|
import androidx.car.app.OnScreenResultListener
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ android {
|
|||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -10,15 +10,24 @@ data class NavigationState (
|
|||||||
val iconMapper: IconMapper = IconMapper(),
|
val iconMapper: IconMapper = IconMapper(),
|
||||||
val navigating: Boolean = false,
|
val navigating: Boolean = false,
|
||||||
val arrived: Boolean = false,
|
val arrived: Boolean = false,
|
||||||
|
// travel message
|
||||||
val travelMessage: String = "",
|
val travelMessage: String = "",
|
||||||
|
// maneuver type
|
||||||
val maneuverType: Int = 0,
|
val maneuverType: Int = 0,
|
||||||
|
// last location
|
||||||
val lastLocation: Location = location(0.0, 0.0),
|
val lastLocation: Location = location(0.0, 0.0),
|
||||||
|
// current location
|
||||||
val currentLocation: Location = location(0.0, 0.0),
|
val currentLocation: Location = location(0.0, 0.0),
|
||||||
|
// bearing of the route
|
||||||
val routeBearing: Float = 0F,
|
val routeBearing: Float = 0F,
|
||||||
// index of current route in the list of routes
|
// index of current route in the list of routes
|
||||||
val currentRouteIndex: Int = 0,
|
val currentRouteIndex: Int = 0,
|
||||||
|
// destination name
|
||||||
val destination: Place = Place(),
|
val destination: Place = Place(),
|
||||||
|
// car connection used
|
||||||
val carConnection: Int = 0,
|
val carConnection: Int = 0,
|
||||||
|
// routing engine used
|
||||||
val routingEngine: Int = 0,
|
val routingEngine: Int = 0,
|
||||||
|
// show next step information
|
||||||
val nextStep: Boolean = false,
|
val nextStep: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.data.tomtom
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import com.kouros.data.BuildConfig
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.EngineType
|
import com.kouros.navigation.data.EngineType
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
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 =
|
private const val tomtomFields =
|
||||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
"{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() {
|
class TomTomRepository : NavigationRepository() {
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ class RouteCalculator(var routeModel: RouteModel) {
|
|||||||
return timeLeft
|
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. */
|
/** Returns the current [Step] left distance in m. */
|
||||||
fun leftStepDistance(): Double {
|
fun leftStepDistance(): Double {
|
||||||
val step = routeModel.route.currentStep()
|
val step = routeModel.route.currentStep()
|
||||||
|
|||||||
Reference in New Issue
Block a user