CarHardwareInfoScreen
This commit is contained in:
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 125
|
versionCode = 131
|
||||||
versionName = "0.3.2.125"
|
versionName = "0.4.0.131"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package com.kouros.navigation.car
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.kouros.navigation.car.navigation.Simulation
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.overpass.Overpass
|
|
||||||
import com.kouros.navigation.data.route.ManeuverType
|
import com.kouros.navigation.data.route.ManeuverType
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
@@ -15,18 +13,13 @@ import com.kouros.navigation.model.RouteModel
|
|||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Before
|
|
||||||
import org.maplibre.compose.expressions.dsl.step
|
|
||||||
import org.maplibre.geojson.Point
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
@@ -635,5 +628,20 @@ class RouteModelTest {
|
|||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
//assertEquals(routeModel.currentStep().leftStepDistance, 0.0, 1.0)
|
//assertEquals(routeModel.currentStep().leftStepDistance, 0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check deviation `() {
|
||||||
|
val navigationViewModel = NavigationViewModel(TomTomRepository())
|
||||||
|
// Schmalkaldener Straße
|
||||||
|
val firstLocation = location(11.579903, 48.186906)
|
||||||
|
routeModel.updateLocation(firstLocation, navigationViewModel)
|
||||||
|
// Frankfurter Ring
|
||||||
|
val location = location(11.579794, 48.187700)
|
||||||
|
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||||
|
val distance = snappedLocation.distanceTo(firstLocation)
|
||||||
|
assertEquals(distance.toDouble(), 12.0, 1.0)
|
||||||
|
assertEquals(snappedLocation.latitude, 48.18691500607897, 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -458,6 +458,44 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the location deviation is acceptable.
|
||||||
|
*/
|
||||||
|
private fun checkLocationDeviation(
|
||||||
|
location: Location,
|
||||||
|
snappedLocation: Location,
|
||||||
|
streetName: String
|
||||||
|
): Boolean {
|
||||||
|
var maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION
|
||||||
|
val speed = surfaceRenderer.speed.value
|
||||||
|
if (speed != null) {
|
||||||
|
when (speed) {
|
||||||
|
in 0.0..10.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION
|
||||||
|
in 10.0..20.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 200
|
||||||
|
in 20.0..30.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 400
|
||||||
|
in 30.0..100.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val distance = location.distanceTo(snappedLocation)
|
||||||
|
Log.d(TAG, "Distance: $distance $maximalRouteDeviation $speed")
|
||||||
|
when {
|
||||||
|
distance > maximalRouteDeviation -> {
|
||||||
|
stopNavigation()
|
||||||
|
navigationScreen.calculateNewRoute(routeModel.navState.destination, distance)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
distance < MAXIMAL_SNAP_CORRECTION -> {
|
||||||
|
surfaceRenderer.updateLocation(snappedLocation, streetName)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
surfaceRenderer.updateLocation(location, streetName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles location updates during active navigation.
|
* Handles location updates during active navigation.
|
||||||
* Snaps location to route and checks for deviation requiring reroute.
|
* Snaps location to route and checks for deviation requiring reroute.
|
||||||
@@ -483,43 +521,6 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
//Log.d(TAG, "UpdateLocation ${end-start} ms")
|
//Log.d(TAG, "UpdateLocation ${end-start} ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the location deviation is acceptable.
|
|
||||||
*/
|
|
||||||
private fun checkLocationDeviation(
|
|
||||||
location: Location,
|
|
||||||
snappedLocation: Location,
|
|
||||||
streetName: String
|
|
||||||
): Boolean {
|
|
||||||
var maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION
|
|
||||||
val speed = surfaceRenderer.speed.value
|
|
||||||
if (speed != null) {
|
|
||||||
when (speed) {
|
|
||||||
in 0.0..10.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION
|
|
||||||
in 10.0..20.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 200
|
|
||||||
in 20.0..30.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 400
|
|
||||||
in 30.0..100.0 -> maximalRouteDeviation = MAXIMAL_ROUTE_DEVIATION + 500
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val distance = location.distanceTo(snappedLocation)
|
|
||||||
when {
|
|
||||||
distance > maximalRouteDeviation -> {
|
|
||||||
stopNavigation()
|
|
||||||
navigationScreen.calculateNewRoute(routeModel.navState.destination, distance)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
distance < MAXIMAL_SNAP_CORRECTION -> {
|
|
||||||
surfaceRenderer.updateLocation(snappedLocation, streetName)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
surfaceRenderer.updateLocation(location, streetName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the navigation screen with new trip information.
|
* Updates the navigation screen with new trip information.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -398,8 +398,11 @@ class SurfaceRenderer(
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
street.value = streetName
|
street.value = streetName
|
||||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
val bearing = if (carOrientation == 999F) {
|
//val bearing =
|
||||||
if (location.hasBearing()) {
|
// location.bearing.toDouble()
|
||||||
|
//carOrientation = bearing.toFloat()
|
||||||
|
// val bearing = if (carOrientation == 999F) {
|
||||||
|
val bearing = if (location.hasBearing()) {
|
||||||
location.bearing.toDouble()
|
location.bearing.toDouble()
|
||||||
} else {
|
} else {
|
||||||
bearingPositive(
|
bearingPositive(
|
||||||
@@ -408,9 +411,10 @@ class SurfaceRenderer(
|
|||||||
cameraPosition.value!!.bearing
|
cameraPosition.value!!.bearing
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
carOrientation = bearing.toFloat()
|
||||||
carOrientation.toDouble()
|
// } else {
|
||||||
}
|
// carOrientation.toDouble()
|
||||||
|
// }
|
||||||
val zoom = if (viewStyle == ViewStyle.VIEW) {
|
val zoom = if (viewStyle == ViewStyle.VIEW) {
|
||||||
calculateZoom(location.speed.toDouble())
|
calculateZoom(location.speed.toDouble())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,481 @@
|
|||||||
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.GuardedBy
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.hardware.CarHardwareManager
|
||||||
|
import androidx.car.app.hardware.common.CarValue
|
||||||
|
import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
||||||
|
import androidx.car.app.hardware.info.CarHardwareLocation
|
||||||
|
import androidx.car.app.hardware.info.CarSensors
|
||||||
|
import androidx.car.app.hardware.info.Compass
|
||||||
|
import androidx.car.app.hardware.info.EnergyProfile
|
||||||
|
import androidx.car.app.hardware.info.ExteriorDimensions
|
||||||
|
import androidx.car.app.hardware.info.Model
|
||||||
|
import androidx.car.app.hardware.info.Speed
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.Pane
|
||||||
|
import androidx.car.app.model.PaneTemplate
|
||||||
|
import androidx.car.app.model.Row
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import com.kouros.data.R
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a screen that show the static information (such as model and energy profile) available
|
||||||
|
* via CarHardware interfaces.
|
||||||
|
*/
|
||||||
|
class CarHardwareInfoScreen(carContext: CarContext) : Screen(carContext) {
|
||||||
|
// Package private for inner class reference
|
||||||
|
|
||||||
|
var mHasLocationPermission: Boolean = false
|
||||||
|
var mHasCompassPermission: Boolean = false
|
||||||
|
|
||||||
|
var mHasSpeedPermission: Boolean = false
|
||||||
|
var mHasModelPermission: Boolean = false
|
||||||
|
var mHasEnergyProfilePermission: Boolean = false
|
||||||
|
var mHasExteriorDimensionsPermission: Boolean = false
|
||||||
|
val mCarHardwareExecutor: Executor = ContextCompat.getMainExecutor(getCarContext())
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mLocation: CarHardwareLocation? = null
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mCompass: Compass? = null
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mSpeed: Speed? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value fetched from CarHardwareManager containing model information.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It is requested asynchronously and can be `null` until the response is
|
||||||
|
* received.
|
||||||
|
*/
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mModel: Model? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value fetched from CarHardwareManager containing what type of fuel/ports the car has.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It is requested asynchronously and can be `null` until the response is
|
||||||
|
* received.
|
||||||
|
*/
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mEnergyProfile: EnergyProfile? = null
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
var mExteriorDimensions: ExteriorDimensions? = null
|
||||||
|
|
||||||
|
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
|
||||||
|
OnCarDataAvailableListener { data ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received locaction: " + data)
|
||||||
|
mLocation = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val mCompassListener: OnCarDataAvailableListener<Compass?> =
|
||||||
|
OnCarDataAvailableListener { data: Compass? ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received compass: " + data)
|
||||||
|
mCompass = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mModelListener: OnCarDataAvailableListener<Model?> =
|
||||||
|
OnCarDataAvailableListener { data: Model? ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received model information: " + data)
|
||||||
|
mModel = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mSpeedListener: OnCarDataAvailableListener<Speed?> =
|
||||||
|
OnCarDataAvailableListener { data: Speed? ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received speed information: " + data)
|
||||||
|
mSpeed = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mEnergyProfileListener: OnCarDataAvailableListener<EnergyProfile?> =
|
||||||
|
OnCarDataAvailableListener { data: EnergyProfile? ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received energy profile information: " + data)
|
||||||
|
mEnergyProfile = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mExteriorDimensionsListener: OnCarDataAvailableListener<ExteriorDimensions?> =
|
||||||
|
OnCarDataAvailableListener { data: ExteriorDimensions? ->
|
||||||
|
synchronized(this) {
|
||||||
|
Log.i(TAG, "Received exterior dimensions: " + data)
|
||||||
|
mExteriorDimensions = data
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
val carHardwareManager =
|
||||||
|
getCarContext().getCarService(CarHardwareManager::class.java)
|
||||||
|
val carInfo = carHardwareManager.carInfo
|
||||||
|
val carSensors = carHardwareManager.carSensors
|
||||||
|
|
||||||
|
// Request any single shot values.
|
||||||
|
synchronized(this@CarHardwareInfoScreen) {
|
||||||
|
mLocation = null
|
||||||
|
try {
|
||||||
|
carSensors.addCarHardwareLocationListener(
|
||||||
|
CarSensors.UPDATE_RATE_NORMAL, mCarHardwareExecutor,
|
||||||
|
carLocationListener
|
||||||
|
)
|
||||||
|
mHasLocationPermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasLocationPermission = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mCompass = null
|
||||||
|
try {
|
||||||
|
carSensors.addCompassListener(
|
||||||
|
CarSensors.UPDATE_RATE_NORMAL, mCarHardwareExecutor,
|
||||||
|
mCompassListener
|
||||||
|
)
|
||||||
|
mHasCompassPermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasCompassPermission = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mSpeed = null
|
||||||
|
try {
|
||||||
|
carInfo.addSpeedListener(mCarHardwareExecutor, mSpeedListener)
|
||||||
|
mHasSpeedPermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasSpeedPermission = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mModel = null
|
||||||
|
try {
|
||||||
|
carInfo.fetchModel(mCarHardwareExecutor, mModelListener)
|
||||||
|
mHasModelPermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasModelPermission = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mEnergyProfile = null
|
||||||
|
try {
|
||||||
|
carInfo.fetchEnergyProfile(mCarHardwareExecutor, mEnergyProfileListener)
|
||||||
|
mHasEnergyProfilePermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasEnergyProfilePermission = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mExteriorDimensions = null
|
||||||
|
try {
|
||||||
|
carInfo.fetchExteriorDimensions(
|
||||||
|
mCarHardwareExecutor,
|
||||||
|
mExteriorDimensionsListener
|
||||||
|
)
|
||||||
|
mHasExteriorDimensionsPermission = true
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
mHasExteriorDimensionsPermission = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
val paneBuilder = Pane.Builder()
|
||||||
|
if (allInfoAvailable()) {
|
||||||
|
val carInfoRowBuilder = Row.Builder()
|
||||||
|
.setTitle(getCarContext().getString(R.string.car_sensors))
|
||||||
|
if (!mHasCompassPermission) {
|
||||||
|
carInfoRowBuilder.addText(getCarContext().getString(R.string.no_model_permission))
|
||||||
|
} else {
|
||||||
|
val info = StringBuilder()
|
||||||
|
synchronized(this@CarHardwareInfoScreen) {
|
||||||
|
if (mCompass != null) {
|
||||||
|
if (mCompass!!.orientations.status == CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(mCompass!!.orientations)
|
||||||
|
}
|
||||||
|
if (mCompass!!.orientations
|
||||||
|
.status == CarValue.STATUS_UNAVAILABLE
|
||||||
|
) {
|
||||||
|
info.append("Compass unavailable")
|
||||||
|
}
|
||||||
|
if (mCompass!!.orientations
|
||||||
|
.status == CarValue.STATUS_UNIMPLEMENTED
|
||||||
|
) {
|
||||||
|
info.append("Compass unimplemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
carInfoRowBuilder.addText(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mHasLocationPermission) {
|
||||||
|
carInfoRowBuilder.addText(carContext.getString(R.string.no_model_permission))
|
||||||
|
} else {
|
||||||
|
val info = StringBuilder()
|
||||||
|
synchronized(this@CarHardwareInfoScreen) {
|
||||||
|
if (mLocation != null) {
|
||||||
|
if (mLocation!!.location.status == CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(mLocation!!.location)
|
||||||
|
}
|
||||||
|
if (mLocation!!.location.status
|
||||||
|
== CarValue.STATUS_UNAVAILABLE
|
||||||
|
) {
|
||||||
|
info.append("Location unavailable")
|
||||||
|
}
|
||||||
|
if (mLocation!!.location
|
||||||
|
.status == CarValue.STATUS_UNIMPLEMENTED
|
||||||
|
) {
|
||||||
|
info.append("Location unimplemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
carInfoRowBuilder.addText(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
paneBuilder.addRow(carInfoRowBuilder.build())
|
||||||
|
|
||||||
|
val modelRowBuilder = Row.Builder()
|
||||||
|
.setTitle(getCarContext().getString(R.string.model_info))
|
||||||
|
if (!mHasModelPermission) {
|
||||||
|
modelRowBuilder.addText(getCarContext().getString(R.string.no_model_permission))
|
||||||
|
} else {
|
||||||
|
val info = StringBuilder()
|
||||||
|
synchronized(this@CarHardwareInfoScreen) {
|
||||||
|
checkNotNull(mModel)
|
||||||
|
if (mModel!!.manufacturer.status != CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(getCarContext().getString(R.string.manufacturer_unavailable))
|
||||||
|
info.append(", ")
|
||||||
|
} else {
|
||||||
|
info.append(mModel!!.manufacturer.getValue())
|
||||||
|
info.append(", ")
|
||||||
|
}
|
||||||
|
if (mModel!!.name.status != CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(getCarContext().getString(R.string.model_unavailable))
|
||||||
|
info.append(", ")
|
||||||
|
} else {
|
||||||
|
info.append(mModel!!.name.getValue())
|
||||||
|
info.append(", ")
|
||||||
|
}
|
||||||
|
if (mModel!!.year.status != CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(getCarContext().getString(R.string.year_unavailable))
|
||||||
|
} else {
|
||||||
|
info.append(mModel!!.year.getValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modelRowBuilder.addText(info)
|
||||||
|
}
|
||||||
|
paneBuilder.addRow(modelRowBuilder.build())
|
||||||
|
|
||||||
|
val speedRowBuilder = Row.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.speed))
|
||||||
|
if (!mHasSpeedPermission) {
|
||||||
|
speedRowBuilder.addText(carContext.getString(R.string.no_speed_permission))
|
||||||
|
} else {
|
||||||
|
if (mSpeed != null) {
|
||||||
|
val info = StringBuilder()
|
||||||
|
synchronized(this@CarHardwareInfoScreen) {
|
||||||
|
if (mSpeed!!.displaySpeedMetersPerSecond.status != CarValue.STATUS_SUCCESS) {
|
||||||
|
info.append(getCarContext().getString(R.string.manufacturer_unavailable))
|
||||||
|
info.append(", ")
|
||||||
|
} else {
|
||||||
|
info.append(mSpeed!!.displaySpeedMetersPerSecond.value)
|
||||||
|
info.append(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
speedRowBuilder.addText(info)
|
||||||
|
} else {
|
||||||
|
val info = StringBuilder()
|
||||||
|
info.append(carContext.getString(R.string.speed_unavailable))
|
||||||
|
speedRowBuilder.addText(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paneBuilder.addRow(speedRowBuilder.build())
|
||||||
|
|
||||||
|
val energyProfileRowBuilder = Row.Builder()
|
||||||
|
.setTitle(getCarContext().getString(R.string.energy_profile))
|
||||||
|
if (!mHasEnergyProfilePermission) {
|
||||||
|
energyProfileRowBuilder.addText(
|
||||||
|
getCarContext()
|
||||||
|
.getString(R.string.no_energy_profile_permission)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val fuelInfo = StringBuilder()
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
if (mEnergyProfile!!.fuelTypes.status != CarValue.STATUS_SUCCESS) {
|
||||||
|
fuelInfo.append(getCarContext().getString(R.string.fuel_types))
|
||||||
|
fuelInfo.append(": ")
|
||||||
|
fuelInfo.append(getCarContext().getString(R.string.unavailable))
|
||||||
|
} else {
|
||||||
|
fuelInfo.append(getCarContext().getString(R.string.fuel_types))
|
||||||
|
fuelInfo.append(": ")
|
||||||
|
for (fuelType in mEnergyProfile!!.fuelTypes.getValue()!!) {
|
||||||
|
fuelInfo.append(fuelTypeAsString(fuelType))
|
||||||
|
fuelInfo.append(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
energyProfileRowBuilder.addText(fuelInfo)
|
||||||
|
val evInfo = StringBuilder()
|
||||||
|
if (mEnergyProfile!!.evConnectorTypes.status
|
||||||
|
!= CarValue.STATUS_SUCCESS
|
||||||
|
) {
|
||||||
|
evInfo.append(" ")
|
||||||
|
evInfo.append(getCarContext().getString(R.string.ev_connector_types))
|
||||||
|
evInfo.append(": ")
|
||||||
|
evInfo.append(getCarContext().getString(R.string.unavailable))
|
||||||
|
} else {
|
||||||
|
evInfo.append(getCarContext().getString(R.string.ev_connector_types))
|
||||||
|
evInfo.append(": ")
|
||||||
|
for (connectorType in mEnergyProfile!!.evConnectorTypes.getValue()!!) {
|
||||||
|
evInfo.append(evConnectorAsString(connectorType))
|
||||||
|
evInfo.append(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
energyProfileRowBuilder.addText(evInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paneBuilder.addRow(energyProfileRowBuilder.build())
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
paneBuilder.addRow(
|
||||||
|
buildExteriorDimensionsRow(
|
||||||
|
mExteriorDimensions,
|
||||||
|
mHasExteriorDimensionsPermission
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paneBuilder.setLoading(true)
|
||||||
|
}
|
||||||
|
return PaneTemplate.Builder(paneBuilder.build())
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setStartHeaderAction(Action.BACK)
|
||||||
|
.setTitle(getCarContext().getString(R.string.car_hardware_info))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allInfoAvailable(): Boolean {
|
||||||
|
synchronized(this) {
|
||||||
|
if (mHasModelPermission && mModel == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (mHasEnergyProfilePermission && mEnergyProfile == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (mHasExteriorDimensionsPermission && mExteriorDimensions == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "showcase"
|
||||||
|
|
||||||
|
private fun fuelTypeAsString(fuelType: Int): String {
|
||||||
|
when (fuelType) {
|
||||||
|
EnergyProfile.FUEL_TYPE_UNLEADED -> return "UNLEADED"
|
||||||
|
EnergyProfile.FUEL_TYPE_LEADED -> return "LEADED"
|
||||||
|
EnergyProfile.FUEL_TYPE_DIESEL_1 -> return "DIESEL_1"
|
||||||
|
EnergyProfile.FUEL_TYPE_DIESEL_2 -> return "DIESEL_2"
|
||||||
|
EnergyProfile.FUEL_TYPE_BIODIESEL -> return "BIODIESEL"
|
||||||
|
EnergyProfile.FUEL_TYPE_E85 -> return "E85"
|
||||||
|
EnergyProfile.FUEL_TYPE_LPG -> return "LPG"
|
||||||
|
EnergyProfile.FUEL_TYPE_CNG -> return "CNG"
|
||||||
|
EnergyProfile.FUEL_TYPE_LNG -> return "LNG"
|
||||||
|
EnergyProfile.FUEL_TYPE_ELECTRIC -> return "ELECTRIC"
|
||||||
|
EnergyProfile.FUEL_TYPE_HYDROGEN -> return "HYDROGEN"
|
||||||
|
EnergyProfile.FUEL_TYPE_OTHER -> return "OTHER"
|
||||||
|
EnergyProfile.FUEL_TYPE_UNKNOWN -> return "UNKNOWN"
|
||||||
|
else -> return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun evConnectorAsString(evConnectorType: Int): String {
|
||||||
|
when (evConnectorType) {
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_J1772 -> return "J1772"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_MENNEKES -> return "MENNEKES"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_CHADEMO -> return "CHADEMO"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_COMBO_1 -> return "COMBO_1"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_COMBO_2 -> return "COMBO_2"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_TESLA_ROADSTER -> return "TESLA_ROADSTER"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_TESLA_HPWC -> return "TESLA_HPWC"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_TESLA_SUPERCHARGER -> return "TESLA_SUPERCHARGER"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_GBT -> return "GBT"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_GBT_DC -> return "GBT_DC"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_SCAME -> return "SCAME"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_OTHER -> return "OTHER"
|
||||||
|
EnergyProfile.EVCONNECTOR_TYPE_UNKNOWN -> return "UNKNOWN"
|
||||||
|
else -> return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildExteriorDimensionsRow(
|
||||||
|
exteriorDimensions: ExteriorDimensions?, hasPermissions: Boolean
|
||||||
|
): Row {
|
||||||
|
val builder = Row.Builder().setTitle("Exterior dimensions")
|
||||||
|
if (!hasPermissions) {
|
||||||
|
builder.addText("Permissions not granted. This vehicle property requires CAR_INFO")
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exteriorDimensions == null) {
|
||||||
|
builder.addText("Pending callback from vehicle fetch request")
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val carValue = exteriorDimensions.getExteriorDimensions()
|
||||||
|
if (carValue.getStatus() != CarValue.STATUS_SUCCESS) {
|
||||||
|
builder.addText("Fetch failed because the vehicle hasn't implemented this field")
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val dimensionsArray = carValue.getValue()
|
||||||
|
if (dimensionsArray == null || dimensionsArray.size != 8) {
|
||||||
|
builder.addText(
|
||||||
|
"Fetch succeeded, but the reply was not an int array of length 8: "
|
||||||
|
+ dimensionsArray.contentToString()
|
||||||
|
)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addText(
|
||||||
|
("Height: " + dimensionsArray[ExteriorDimensions.HEIGHT_INDEX]
|
||||||
|
+ ", Length: " + dimensionsArray[ExteriorDimensions.LENGTH_INDEX]
|
||||||
|
+ ", Width: " + dimensionsArray[ExteriorDimensions.WIDTH_INDEX]
|
||||||
|
+ ", Width + mirrors: "
|
||||||
|
+ dimensionsArray[ExteriorDimensions.WIDTH_INCLUDING_MIRRORS_INDEX])
|
||||||
|
)
|
||||||
|
builder.addText(
|
||||||
|
("Wheel base: " + dimensionsArray[ExteriorDimensions.WHEEL_BASE_INDEX]
|
||||||
|
+ ", Front width: " + dimensionsArray[ExteriorDimensions.TRACK_WIDTH_FRONT_INDEX]
|
||||||
|
+ ", Rear width: " + dimensionsArray[ExteriorDimensions.TRACK_WIDTH_REAR_INDEX]
|
||||||
|
+ ", Turning radius: "
|
||||||
|
+ dimensionsArray[ExteriorDimensions.CURB_TO_CURB_TURNING_RADIUS_INDEX])
|
||||||
|
)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -478,7 +478,7 @@ open class NavigationScreen(
|
|||||||
val mainThreadHandler = Handler(carContext.mainLooper)
|
val mainThreadHandler = Handler(carContext.mainLooper)
|
||||||
mainThreadHandler.post {
|
mainThreadHandler.post {
|
||||||
reRouteTimer?.cancel()
|
reRouteTimer?.cancel()
|
||||||
reRouteTimer = object : CountDownTimer(5000, 1000) {
|
reRouteTimer = object : CountDownTimer(3000, 1000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
navigationType = NavigationType.NAVIGATION
|
navigationType = NavigationType.NAVIGATION
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.kouros.navigation.car.screen.settings
|
package com.kouros.navigation.car.screen.settings
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.annotations.ExperimentalCarApi
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
import androidx.car.app.model.Header
|
import androidx.car.app.model.Header
|
||||||
@@ -16,6 +18,7 @@ import androidx.lifecycle.Observer
|
|||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.car.screen.CarHardwareInfoScreen
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@@ -37,6 +40,7 @@ class SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCarApi::class)
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
settingsViewModel.guidanceAudio.asLiveData().observe(this, Observer {
|
settingsViewModel.guidanceAudio.asLiveData().observe(this, Observer {
|
||||||
audioToggleState = settingsViewModel.guidanceAudio.value == 1
|
audioToggleState = settingsViewModel.guidanceAudio.value == 1
|
||||||
@@ -94,6 +98,13 @@ class SettingsScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
listBuilder.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
CarHardwareInfoScreen(carContext),
|
||||||
|
R.string.model_info
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
listBuilder.addItem(
|
listBuilder.addItem(
|
||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
CarSettings(carContext, navigationViewModel),
|
CarSettings(carContext, navigationViewModel),
|
||||||
|
|||||||
@@ -74,4 +74,22 @@
|
|||||||
<string name="alternative_routes">Alternative Routen</string>
|
<string name="alternative_routes">Alternative Routen</string>
|
||||||
<string name="wait">Warten</string>
|
<string name="wait">Warten</string>
|
||||||
<string name="restaurant">Restaurant</string>
|
<string name="restaurant">Restaurant</string>
|
||||||
|
|
||||||
|
<!-- CarHardwareInfoScreen -->
|
||||||
|
<string name="car_hardware_info">Car Hardware Information</string>
|
||||||
|
<string name="model_info">Model Information</string>
|
||||||
|
<string name="no_model_permission">No Model Permission</string>
|
||||||
|
<string name="no_speed_permission">No Speed Permission</string>
|
||||||
|
<string name="manufacturer_unavailable">Manufacturer unavailable</string>
|
||||||
|
<string name="model_unavailable">Model unavailable</string>
|
||||||
|
<string name="year_unavailable">Year unavailable</string>
|
||||||
|
<string name="energy_profile">Energy Profile</string>
|
||||||
|
<string name="no_energy_profile_permission">No Energy Profile Permission</string>
|
||||||
|
<string name="fuel_types">Fuel Types</string>
|
||||||
|
<string name="unavailable">Unavailable</string>
|
||||||
|
<string name="ev_connector_types">EV Connector Types</string>
|
||||||
|
<string name="car_sensors">Car Sensors</string>
|
||||||
|
<string name="speed">Speed</string>
|
||||||
|
<string name="speed_unavailable">Speed unavailable</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -58,4 +58,22 @@
|
|||||||
<string name="alternative_routes">Εναλλακτικές διαδρομές</string>
|
<string name="alternative_routes">Εναλλακτικές διαδρομές</string>
|
||||||
<string name="wait">Περιμένετε</string>
|
<string name="wait">Περιμένετε</string>
|
||||||
<string name="restaurant">Restaurant</string>
|
<string name="restaurant">Restaurant</string>
|
||||||
|
|
||||||
|
<!-- CarHardwareInfoScreen -->
|
||||||
|
<string name="car_hardware_info">Car Hardware Information</string>
|
||||||
|
<string name="model_info">Model Information</string>
|
||||||
|
<string name="no_model_permission">No Model Permission</string>
|
||||||
|
<string name="no_speed_permission">No Speed Permission</string>
|
||||||
|
<string name="manufacturer_unavailable">Manufacturer unavailable</string>
|
||||||
|
<string name="model_unavailable">Model unavailable</string>
|
||||||
|
<string name="year_unavailable">Year unavailable</string>
|
||||||
|
<string name="energy_profile">Energy Profile</string>
|
||||||
|
<string name="no_energy_profile_permission">No Energy Profile Permission</string>
|
||||||
|
<string name="fuel_types">Fuel Types</string>
|
||||||
|
<string name="unavailable">Unavailable</string>
|
||||||
|
<string name="ev_connector_types">EV Connector Types</string>
|
||||||
|
<string name="car_sensors">Car Sensors</string>
|
||||||
|
<string name="speed">Speed</string>
|
||||||
|
<string name="speed_unavailable">Speed unavailable</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -58,4 +58,22 @@
|
|||||||
<string name="alternative_routes">Alternatywne trasy</string>
|
<string name="alternative_routes">Alternatywne trasy</string>
|
||||||
<string name="wait">Czekaj</string>
|
<string name="wait">Czekaj</string>
|
||||||
<string name="restaurant">Restaurant</string>
|
<string name="restaurant">Restaurant</string>
|
||||||
|
|
||||||
|
<!-- CarHardwareInfoScreen -->
|
||||||
|
<string name="car_hardware_info">Car Hardware Information</string>
|
||||||
|
<string name="model_info">Model Information</string>
|
||||||
|
<string name="no_speed_permission">No Speed Permission</string>
|
||||||
|
<string name="no_model_permission">No Model Permission</string>
|
||||||
|
<string name="manufacturer_unavailable">Manufacturer unavailable</string>
|
||||||
|
<string name="model_unavailable">Model unavailable</string>
|
||||||
|
<string name="year_unavailable">Year unavailable</string>
|
||||||
|
<string name="energy_profile">Energy Profile</string>
|
||||||
|
<string name="no_energy_profile_permission">No Energy Profile Permission</string>
|
||||||
|
<string name="fuel_types">Fuel Types</string>
|
||||||
|
<string name="unavailable">Unavailable</string>
|
||||||
|
<string name="ev_connector_types">EV Connector Types</string>
|
||||||
|
<string name="car_sensors">Car Sensors</string>
|
||||||
|
<string name="speed">Speed</string>
|
||||||
|
<string name="speed_unavailable">Speed unavailable</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -61,4 +61,22 @@
|
|||||||
<string name="alternative_routes">Alternative routes</string>
|
<string name="alternative_routes">Alternative routes</string>
|
||||||
<string name="wait">Wait</string>
|
<string name="wait">Wait</string>
|
||||||
<string name="restaurant">Restaurant</string>
|
<string name="restaurant">Restaurant</string>
|
||||||
|
|
||||||
|
<!-- CarHardwareInfoScreen -->
|
||||||
|
<string name="car_hardware_info">Car Hardware Information</string>
|
||||||
|
<string name="model_info">Model Information</string>
|
||||||
|
<string name="no_model_permission">No Model Permission</string>
|
||||||
|
<string name="no_speed_permission">No Speed Permission</string>
|
||||||
|
<string name="manufacturer_unavailable">Manufacturer unavailable</string>
|
||||||
|
<string name="model_unavailable">Model unavailable</string>
|
||||||
|
<string name="year_unavailable">Year unavailable</string>
|
||||||
|
<string name="energy_profile">Energy Profile</string>
|
||||||
|
<string name="no_energy_profile_permission">No Energy Profile Permission</string>
|
||||||
|
<string name="fuel_types">Fuel Types</string>
|
||||||
|
<string name="unavailable">Unavailable</string>
|
||||||
|
<string name="ev_connector_types">EV Connector Types</string>
|
||||||
|
<string name="car_sensors">Car Sensors</string>
|
||||||
|
<string name="speed">Speed</string>
|
||||||
|
<string name="speed_unavailable">Speed unavailable</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user