From e33eba0d51b1fbc93a0c58b9d11932d60e153eac Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 17 Jun 2026 09:15:23 +0200 Subject: [PATCH] CarHardwareInfoScreen --- app/build.gradle.kts | 4 +- .../kouros/navigation/car/RouteModelTest.kt | 30 +- .../navigation/car/NavigationSession.kt | 75 +-- .../kouros/navigation/car/SurfaceRenderer.kt | 14 +- .../car/screen/CarHardwareInfoScreen.kt | 481 ++++++++++++++++++ .../navigation/car/screen/NavigationScreen.kt | 2 +- .../car/screen/settings/SettingsScreen.kt | 11 + .../data/src/main/res/values-de/strings.xml | 18 + .../data/src/main/res/values-el/strings.xml | 18 + .../data/src/main/res/values-pl/strings.xml | 18 + common/data/src/main/res/values/strings.xml | 18 + 11 files changed, 633 insertions(+), 56 deletions(-) create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/CarHardwareInfoScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 971533e..87a8b19 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt index 03c06c3..19a04af 100644 --- a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt +++ b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt @@ -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) + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index 0654c72..2412093 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -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. */ diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index 0a01133..e7a32c6 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -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 { diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CarHardwareInfoScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CarHardwareInfoScreen.kt new file mode 100644 index 0000000..a1fa1bf --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CarHardwareInfoScreen.kt @@ -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 = + OnCarDataAvailableListener { data -> + synchronized(this) { + Log.i(TAG, "Received locaction: " + data) + mLocation = data + invalidate() + } + } + val mCompassListener: OnCarDataAvailableListener = + OnCarDataAvailableListener { data: Compass? -> + synchronized(this) { + Log.i(TAG, "Received compass: " + data) + mCompass = data + invalidate() + } + } + + var mModelListener: OnCarDataAvailableListener = + OnCarDataAvailableListener { data: Model? -> + synchronized(this) { + Log.i(TAG, "Received model information: " + data) + mModel = data + invalidate() + } + } + + var mSpeedListener: OnCarDataAvailableListener = + OnCarDataAvailableListener { data: Speed? -> + synchronized(this) { + Log.i(TAG, "Received speed information: " + data) + mSpeed = data + invalidate() + } + } + + var mEnergyProfileListener: OnCarDataAvailableListener = + OnCarDataAvailableListener { data: EnergyProfile? -> + synchronized(this) { + Log.i(TAG, "Received energy profile information: " + data) + mEnergyProfile = data + invalidate() + } + } + + var mExteriorDimensionsListener: OnCarDataAvailableListener = + 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() + } + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index 01aeebc..eba7d82 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt index 0ff6900..d39f7ed 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt @@ -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), diff --git a/common/data/src/main/res/values-de/strings.xml b/common/data/src/main/res/values-de/strings.xml index 1450143..802984a 100644 --- a/common/data/src/main/res/values-de/strings.xml +++ b/common/data/src/main/res/values-de/strings.xml @@ -74,4 +74,22 @@ Alternative Routen Warten Restaurant + + + Car Hardware Information + Model Information + No Model Permission + No Speed Permission + Manufacturer unavailable + Model unavailable + Year unavailable + Energy Profile + No Energy Profile Permission + Fuel Types + Unavailable + EV Connector Types + Car Sensors + Speed + Speed unavailable + diff --git a/common/data/src/main/res/values-el/strings.xml b/common/data/src/main/res/values-el/strings.xml index f392a89..1fc7d14 100644 --- a/common/data/src/main/res/values-el/strings.xml +++ b/common/data/src/main/res/values-el/strings.xml @@ -58,4 +58,22 @@ Εναλλακτικές διαδρομές Περιμένετε Restaurant + + + Car Hardware Information + Model Information + No Model Permission + No Speed Permission + Manufacturer unavailable + Model unavailable + Year unavailable + Energy Profile + No Energy Profile Permission + Fuel Types + Unavailable + EV Connector Types + Car Sensors + Speed + Speed unavailable + diff --git a/common/data/src/main/res/values-pl/strings.xml b/common/data/src/main/res/values-pl/strings.xml index 6eb8306..bd2cc99 100644 --- a/common/data/src/main/res/values-pl/strings.xml +++ b/common/data/src/main/res/values-pl/strings.xml @@ -58,4 +58,22 @@ Alternatywne trasy Czekaj Restaurant + + + Car Hardware Information + Model Information + No Speed Permission + No Model Permission + Manufacturer unavailable + Model unavailable + Year unavailable + Energy Profile + No Energy Profile Permission + Fuel Types + Unavailable + EV Connector Types + Car Sensors + Speed + Speed unavailable + diff --git a/common/data/src/main/res/values/strings.xml b/common/data/src/main/res/values/strings.xml index c0894f9..1873565 100644 --- a/common/data/src/main/res/values/strings.xml +++ b/common/data/src/main/res/values/strings.xml @@ -61,4 +61,22 @@ Alternative routes Wait Restaurant + + + Car Hardware Information + Model Information + No Model Permission + No Speed Permission + Manufacturer unavailable + Model unavailable + Year unavailable + Energy Profile + No Energy Profile Permission + Fuel Types + Unavailable + EV Connector Types + Car Sensors + Speed + Speed unavailable + \ No newline at end of file