Merge "Format sensor values and imperial unit conversions"
diff --git a/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml b/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml
new file mode 100644
index 0000000..05eef22
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/res/values-en-rUS/dimens.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--                                   -->
+    <!-- Sensor value conversion constants -->
+    <!--                                   -->
+    <!-- Speed: meters per second to miles per hour -->
+    <item name="speed_factor" format="float" type="dimen">2.236936</item>
+    <!-- Distance: miles to meters -->
+    <item name="distance_factor" format="float" type="dimen">1609.344</item>
+</resources>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/values/dimens.xml b/tests/DirectRenderingClusterSample/res/values/dimens.xml
index 2979b36..6d22a70 100644
--- a/tests/DirectRenderingClusterSample/res/values/dimens.xml
+++ b/tests/DirectRenderingClusterSample/res/values/dimens.xml
@@ -40,4 +40,12 @@
     <dimen name="laneview_height">25dp</dimen>
     <dimen name="lane_width">50dp</dimen>
     <dimen name="lane_height">50dp</dimen>
+
+    <!--                                   -->
+    <!-- Sensor value conversion constants -->
+    <!--                                   -->
+    <!-- Speed: meters per second to kilometers per hour -->
+    <item name="speed_factor" format="float" type="dimen">3.6</item>
+    <!-- Distance: kilometers to meters -->
+    <item name="distance_factor" format="float" type="dimen">1000</item>
 </resources>
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
index a53f4ba..d1e6ee6 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
@@ -28,6 +28,7 @@
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.util.Log;
+import android.util.TypedValue;
 
 import androidx.annotation.NonNull;
 import androidx.core.util.Preconditions;
@@ -36,6 +37,7 @@
 import androidx.lifecycle.MutableLiveData;
 import androidx.lifecycle.Transformations;
 
+import java.text.DecimalFormat;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -48,6 +50,9 @@
 
     private static final int PROPERTIES_REFRESH_RATE_UI = 5;
 
+    private float mSpeedFactor;
+    private float mDistanceFactor;
+
     public enum NavigationActivityState {
         /** No activity has been selected to be displayed on the navigation fragment yet */
         NOT_SELECTED,
@@ -110,42 +115,43 @@
         }
     }
 
-
     private CarPropertyManager.CarPropertyEventListener mCarPropertyEventListener =
             new CarPropertyManager.CarPropertyEventListener() {
-        @Override
-        public void onChangeEvent(CarPropertyValue value) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "CarProperty change: property " + value.getPropertyId() + ", area"
-                        + value.getAreaId() + ", value: " + value.getValue());
-            }
-            for (Sensor<?> sensorId : Sensors.getInstance()
-                    .getSensorsForPropertyId(value.getPropertyId())) {
-                if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
-                        || (sensorId.mAreaId & value.getAreaId()) != 0) {
-                    setSensorValue(sensorId, value);
+                @Override
+                public void onChangeEvent(CarPropertyValue value) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "CarProperty change: property " + value.getPropertyId() + ", area"
+                                        + value.getAreaId() + ", value: " + value.getValue());
+                    }
+                    for (Sensor<?> sensorId : Sensors.getInstance()
+                            .getSensorsForPropertyId(value.getPropertyId())) {
+                        if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
+                                || (sensorId.mAreaId & value.getAreaId()) != 0) {
+                            setSensorValue(sensorId, value);
+                        }
+                    }
                 }
-            }
-        }
 
-        @Override
-        public void onErrorEvent(int propId, int zone) {
-            for (Sensor<?> sensorId : Sensors.getInstance().getSensorsForPropertyId(propId)) {
-                if (sensorId.mAreaId == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL
-                        || (sensorId.mAreaId & zone) != 0) {
-                    setSensorValue(sensorId, null);
+                @Override
+                public void onErrorEvent(int propId, int zone) {
+                    for (Sensor<?> sensorId : Sensors.getInstance().getSensorsForPropertyId(
+                            propId)) {
+                        if (sensorId.mAreaId == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL
+                                || (sensorId.mAreaId & zone) != 0) {
+                            setSensorValue(sensorId, null);
+                        }
+                    }
                 }
-            }
-        }
 
-        private <T> void setSensorValue(Sensor<T> id, CarPropertyValue<?> value) {
-            T newValue = value != null ? id.mAdapter.apply(value) : null;
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Sensor " + id.mName + " = " + newValue);
-            }
-            getSensorMutableLiveData(id).setValue(newValue);
-        }
-    };
+                private <T> void setSensorValue(Sensor<T> id, CarPropertyValue<?> value) {
+                    T newValue = value != null ? id.mAdapter.apply(value) : null;
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Sensor " + id.mName + " = " + newValue);
+                    }
+                    getSensorMutableLiveData(id).setValue(newValue);
+                }
+            };
 
     /**
      * New {@link ClusterViewModel} instance
@@ -154,6 +160,13 @@
         super(application);
         mCar = Car.createCar(application, mCarServiceConnection);
         mCar.connect();
+
+        TypedValue tv = new TypedValue();
+        getApplication().getResources().getValue(R.dimen.speed_factor, tv, true);
+        mSpeedFactor = tv.getFloat();
+
+        getApplication().getResources().getValue(R.dimen.distance_factor, tv, true);
+        mDistanceFactor = tv.getFloat();
     }
 
     @Override
@@ -187,7 +200,7 @@
      * own data type. The list of all supported sensors can be found at {@link Sensors}
      *
      * @param sensor sensor to observe
-     * @param <T> data type of such sensor
+     * @param <T>    data type of such sensor
      */
     @SuppressWarnings("unchecked")
     @NonNull
@@ -199,8 +212,8 @@
      * Returns the current value of the sensor, directly from the VHAL.
      *
      * @param sensor sensor to read
-     * @param <V> VHAL data type
-     * @param <T> data type of such sensor
+     * @param <V>    VHAL data type
+     * @param <T>    data type of such sensor
      */
     @Nullable
     public <T> T getSensorValue(@NonNull Sensor<T> sensor) {
@@ -210,21 +223,48 @@
     }
 
     /**
-     * Returns a {@link LiveData} that tracks the fuel level in a range from 0.0 to 1.0.
+     * Returns a {@link LiveData} that tracks the fuel level in a range from 0 to 100.
      */
-    public LiveData<Float> getFuelLevel() {
+    public LiveData<Integer> getFuelLevel() {
         return Transformations.map(getSensor(Sensors.SENSOR_FUEL), (fuelValue) -> {
             Float fuelCapacityValue = getSensorValue(Sensors.SENSOR_FUEL_CAPACITY);
             if (fuelValue == null || fuelCapacityValue == null || fuelCapacityValue == 0) {
                 return null;
             }
             if (fuelValue < 0.0f) {
-                return 0.0f;
+                return 0;
             }
             if (fuelValue > fuelCapacityValue) {
-                return 1.0f;
+                return 100;
             }
-            return fuelValue / fuelCapacityValue;
+            return Math.round(fuelValue / (fuelCapacityValue * 100f));
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the RPM x 1000
+     */
+    public LiveData<String> getRPM() {
+        return Transformations.map(getSensor(Sensors.SENSOR_RPM), (rpmValue) -> {
+            return new DecimalFormat("#0.0").format(rpmValue / 1000f);
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the speed in either mi/h or km/h depending on locale.
+     */
+    public LiveData<Integer> getSpeed() {
+        return Transformations.map(getSensor(Sensors.SENSOR_SPEED), (speedValue) -> {
+            return Math.round(speedValue * mSpeedFactor);
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the range the vehicle has until it runs out of gas.
+     */
+    public LiveData<Integer> getRange() {
+        return Transformations.map(getSensor(Sensors.SENSOR_FUEL_RANGE), (rangeValue) -> {
+            return Math.round(rangeValue / mDistanceFactor);
         });
     }
 
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 13a1399..9f4f931 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -227,12 +227,9 @@
         mClusterViewModel.getSensor(Sensors.SENSOR_GEAR).observe(this, this::updateSelectedGear);
 
         registerSensor(findViewById(R.id.info_fuel), mClusterViewModel.getFuelLevel());
-        registerSensor(findViewById(R.id.info_speed),
-                mClusterViewModel.getSensor(Sensors.SENSOR_SPEED));
-        registerSensor(findViewById(R.id.info_range),
-                mClusterViewModel.getSensor(Sensors.SENSOR_FUEL_RANGE));
-        registerSensor(findViewById(R.id.info_rpm),
-                mClusterViewModel.getSensor(Sensors.SENSOR_RPM));
+        registerSensor(findViewById(R.id.info_speed), mClusterViewModel.getSpeed());
+        registerSensor(findViewById(R.id.info_range), mClusterViewModel.getRange());
+        registerSensor(findViewById(R.id.info_rpm), mClusterViewModel.getRPM());
 
         mActivityMonitor.start();
 
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
index 6e49931..90d6350 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
@@ -60,12 +60,12 @@
             "RPM", VehiclePropertyIds.ENGINE_RPM, GLOBAL_AREA_ID,
             VehiclePropertyType.FLOAT,
             value -> (Float) value.getValue());
-    /** Fuel range in kilometers */
+    /** Fuel range in meters */
     public static final Sensor<Float> SENSOR_FUEL_RANGE = registerSensor(
             "Fuel Range", VehiclePropertyIds.RANGE_REMAINING, GLOBAL_AREA_ID,
             VehiclePropertyType.FLOAT,
             value -> (Float) value.getValue());
-    /** Speed in kph */
+    /** Speed in meters per second */
     public static final Sensor<Float> SENSOR_SPEED = registerSensor(
             "Speed", VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID,
             VehiclePropertyType.FLOAT,