From 7420101153acd5faf8d405015252b63a66df728f Mon Sep 17 00:00:00 2001 From: johan12345 Date: Sat, 2 Jul 2022 13:38:37 +0200 Subject: [PATCH] Android Automotive OS: add driving direction to vehicle data fixes #188 --- .../net/vonforst/evmap/auto/CarSensors.kt | 140 ++++++++++++++++++ .../vonforst/evmap/auto/VehicleDataScreen.kt | 18 ++- 2 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 app/src/google/java/net/vonforst/evmap/auto/CarSensors.kt diff --git a/app/src/google/java/net/vonforst/evmap/auto/CarSensors.kt b/app/src/google/java/net/vonforst/evmap/auto/CarSensors.kt new file mode 100644 index 00000000..ce630928 --- /dev/null +++ b/app/src/google/java/net/vonforst/evmap/auto/CarSensors.kt @@ -0,0 +1,140 @@ +package net.vonforst.evmap.auto + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import androidx.car.app.CarContext +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.* +import androidx.car.app.hardware.info.CarSensors.UpdateRate +import net.vonforst.evmap.BuildConfig +import java.util.concurrent.Executor + +/** + * CarSensors is not yet implemented for Android Automotive OS + * (see docs at https://developer.android.com/reference/androidx/car/app/hardware/info/CarSensors) + * so we provide our own implementation based on SensorManager APIs. + */ +val CarContext.patchedCarSensors: CarSensors + get() = if (BuildConfig.FLAVOR_automotive != "automotive") { + (this.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager).carSensors + } else { + CarSensorsWrapper(this) + } + +class CarSensorsWrapper(carContext: CarContext) : + CarSensors { + private val sensorManager = carContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val compassListeners: MutableMap, SensorEventListener> = + mutableMapOf() + + override fun addAccelerometerListener( + rate: Int, + executor: Executor, + listener: OnCarDataAvailableListener + ) { + TODO("Not yet implemented") + } + + override fun removeAccelerometerListener(listener: OnCarDataAvailableListener) { + TODO("Not yet implemented") + } + + override fun addGyroscopeListener( + rate: Int, + executor: Executor, + listener: OnCarDataAvailableListener + ) { + TODO("Not yet implemented") + } + + override fun removeGyroscopeListener(listener: OnCarDataAvailableListener) { + TODO("Not yet implemented") + } + + override fun addCompassListener( + rate: Int, + executor: Executor, + listener: OnCarDataAvailableListener + ) { + val magSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) + val accSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + + if (magSensor == null) { + executor.execute { + listener.onCarDataAvailable(Compass(CarValue(null, 0, CarValue.STATUS_UNAVAILABLE))) + } + return + } + val sensorListener = object : SensorEventListener { + var magValues: FloatArray? = null + + // AAOS cars may not provide an acceleration sensor, so we assume acceleration based on + // Earth's gravity. May not be correct when driving on other planets. + var accValues = floatArrayOf(0f, 0f, SensorManager.GRAVITY_EARTH) + val rotMatrix = FloatArray(9) + val orientation = FloatArray(3) + + override fun onSensorChanged(event: SensorEvent) { + when (event.sensor) { + magSensor -> magValues = event.values + accSensor -> accValues = event.values + } + if (magValues == null) return + + SensorManager.getRotationMatrix(rotMatrix, null, accValues, magValues) + SensorManager.getOrientation(rotMatrix, orientation) + val compassDegrees = orientation.map { Math.toDegrees(it.toDouble()).toFloat() } + executor.execute { + listener.onCarDataAvailable( + Compass( + CarValue( + compassDegrees, + event.timestamp, + CarValue.STATUS_SUCCESS + ) + ) + ) + } + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + } + + } + compassListeners[listener] = sensorListener + sensorManager.registerListener(sensorListener, magSensor, mapRate(rate)) + accSensor?.let { sensorManager.registerListener(sensorListener, it, mapRate(rate)) } + } + + private fun mapRate(@UpdateRate rate: Int): Int { + return when (rate) { + CarSensors.UPDATE_RATE_NORMAL -> SensorManager.SENSOR_DELAY_NORMAL + CarSensors.UPDATE_RATE_UI -> SensorManager.SENSOR_DELAY_UI + CarSensors.UPDATE_RATE_FASTEST -> SensorManager.SENSOR_DELAY_FASTEST + else -> throw IllegalArgumentException() + } + } + + override fun removeCompassListener(listener: OnCarDataAvailableListener) { + compassListeners[listener]?.let { + sensorManager.unregisterListener(it) + } + } + + override fun addCarHardwareLocationListener( + rate: Int, + executor: Executor, + listener: OnCarDataAvailableListener + ) { + TODO("Not yet implemented") + } + + override fun removeCarHardwareLocationListener(listener: OnCarDataAvailableListener) { + TODO("Not yet implemented") + } +} diff --git a/app/src/google/java/net/vonforst/evmap/auto/VehicleDataScreen.kt b/app/src/google/java/net/vonforst/evmap/auto/VehicleDataScreen.kt index d6559cea..afb69cb8 100644 --- a/app/src/google/java/net/vonforst/evmap/auto/VehicleDataScreen.kt +++ b/app/src/google/java/net/vonforst/evmap/auto/VehicleDataScreen.kt @@ -21,7 +21,9 @@ import kotlin.math.min import kotlin.math.roundToInt class VehicleDataScreen(ctx: CarContext) : Screen(ctx), LifecycleObserver { - private val hardwareMan = ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager + private val carInfo = + (ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager).carInfo + private val carSensors = carContext.patchedCarSensors private var model: Model? = null private var energyLevel: EnergyLevel? = null private var speed: Speed? = null @@ -232,15 +234,15 @@ class VehicleDataScreen(ctx: CarContext) : Screen(ctx), LifecycleObserver { println("Setting up energy level listener") val exec = ContextCompat.getMainExecutor(carContext) - hardwareMan.carInfo.addEnergyLevelListener(exec, ::onEnergyLevelUpdated) - hardwareMan.carInfo.addSpeedListener(exec, ::onSpeedUpdated) - hardwareMan.carSensors.addCompassListener( + carInfo.addEnergyLevelListener(exec, ::onEnergyLevelUpdated) + carInfo.addSpeedListener(exec, ::onSpeedUpdated) + carSensors.addCompassListener( CarSensors.UPDATE_RATE_NORMAL, exec, ::onCompassUpdated ) - hardwareMan.carInfo.fetchModel(exec) { + carInfo.fetchModel(exec) { this.model = it invalidate() } @@ -249,9 +251,9 @@ class VehicleDataScreen(ctx: CarContext) : Screen(ctx), LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) private fun removeListeners() { println("Removing energy level listener") - hardwareMan.carInfo.removeEnergyLevelListener(::onEnergyLevelUpdated) - hardwareMan.carInfo.removeSpeedListener(::onSpeedUpdated) - hardwareMan.carSensors.removeCompassListener(::onCompassUpdated) + carInfo.removeEnergyLevelListener(::onEnergyLevelUpdated) + carInfo.removeSpeedListener(::onSpeedUpdated) + carSensors.removeCompassListener(::onCompassUpdated) } private fun permissionsGranted(): Boolean =