CarHardwareInfoScreen

This commit is contained in:
Dimitris
2026-06-17 09:15:23 +02:00
parent a3b22c386b
commit e33eba0d51
11 changed files with 633 additions and 56 deletions
+2 -2
View File
@@ -17,8 +17,8 @@ android {
applicationId = "com.kouros.navigation"
minSdk = 33
targetSdk = 37
versionCode = 125
versionName = "0.3.2.125"
versionCode = 131
versionName = "0.4.0.131"
base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -2,12 +2,10 @@ package com.kouros.navigation.car
import android.location.Location
import android.location.LocationManager
import androidx.test.platform.app.InstrumentationRegistry
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.RouteEngine
import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.data.route.ManeuverType
import com.kouros.navigation.data.tomtom.TomTomRepository
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.getSettingsRepository
import com.kouros.navigation.utils.location
import kotlinx.coroutines.delay
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.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.
*
@@ -635,5 +628,20 @@ class RouteModelTest {
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
//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.
* 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")
}
/**
* 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.
*/
@@ -398,8 +398,11 @@ class SurfaceRenderer(
synchronized(this) {
street.value = streetName
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = if (carOrientation == 999F) {
if (location.hasBearing()) {
//val bearing =
// location.bearing.toDouble()
//carOrientation = bearing.toFloat()
// val bearing = if (carOrientation == 999F) {
val bearing = if (location.hasBearing()) {
location.bearing.toDouble()
} else {
bearingPositive(
@@ -408,9 +411,10 @@ class SurfaceRenderer(
cameraPosition.value!!.bearing
)
}
} else {
carOrientation.toDouble()
}
carOrientation = bearing.toFloat()
// } else {
// carOrientation.toDouble()
// }
val zoom = if (viewStyle == ViewStyle.VIEW) {
calculateZoom(location.speed.toDouble())
} 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)
mainThreadHandler.post {
reRouteTimer?.cancel()
reRouteTimer = object : CountDownTimer(5000, 1000) {
reRouteTimer = object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
navigationType = NavigationType.NAVIGATION
@@ -1,7 +1,9 @@
package com.kouros.navigation.car.screen.settings
import androidx.annotation.OptIn
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.annotations.ExperimentalCarApi
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Header
@@ -16,6 +18,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.car.screen.CarHardwareInfoScreen
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
@@ -37,6 +40,7 @@ class SettingsScreen(
}
}
@OptIn(ExperimentalCarApi::class)
override fun onGetTemplate(): Template {
settingsViewModel.guidanceAudio.asLiveData().observe(this, Observer {
audioToggleState = settingsViewModel.guidanceAudio.value == 1
@@ -94,6 +98,13 @@ class SettingsScreen(
)
)
listBuilder.addItem(
buildRowForTemplate(
CarHardwareInfoScreen(carContext),
R.string.model_info
)
)
listBuilder.addItem(
buildRowForTemplate(
CarSettings(carContext, navigationViewModel),
@@ -74,4 +74,22 @@
<string name="alternative_routes">Alternative Routen</string>
<string name="wait">Warten</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>
@@ -58,4 +58,22 @@
<string name="alternative_routes">Εναλλακτικές διαδρομές</string>
<string name="wait">Περιμένετε</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>
@@ -58,4 +58,22 @@
<string name="alternative_routes">Alternatywne trasy</string>
<string name="wait">Czekaj</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>
@@ -61,4 +61,22 @@
<string name="alternative_routes">Alternative routes</string>
<string name="wait">Wait</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>