Merge "Implement UnitsBasePreferenceController with application to UnitsTemperaturePreferenceController" into pi-car-dev
diff --git a/res/drawable/ic_settings_units.xml b/res/drawable/ic_settings_units.xml
new file mode 100644
index 0000000..22142c8
--- /dev/null
+++ b/res/drawable/ic_settings_units.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/icon_size"
+    android:height="@dimen/icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?attr/iconColor"
+        android:pathData="M17.66,17.66l-1.06,1.06l-0.71-0.71l1.06-1.06l-1.94-1.94l-1.06,1.06l-0.71-0.71l1.06-1.06l-1.94-1.94l-1.06,1.06    l-0.71-0.71l1.06-1.06L9.7,9.7l-1.06,1.06l-0.71-0.71l1.06-1.06L7.05,7.05L5.99,8.11L5.28,7.4l1.06-1.06L4,4v14c0,1.1,0.9,2,2,2    h14L17.66,17.66z M7,17v-5.76L12.76,17H7z"/>
+</vector>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index fc4f338..fc9c2d7 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -265,6 +265,14 @@
     <string name="pk_default_alarm" translatable="false">default_alarm</string>
     <string name="pk_sounds_extra_settings" translatable="false">sounds_extra_settings</string>
 
+    <!-- Units Settings -->
+    <string name="pk_units_settings_entry" translatable="false">units_settings_entry</string>
+    <string name="pk_units_distance_and_volume" translatable="false">units_distance_and_volume</string>
+    <string name="pk_units_speed">units_speed</string>
+    <string name="pk_units_fuel_consumption">units_fuel_consumption</string>
+    <string name="pk_units_temperature">units_temperature</string>
+    <string name="pk_units_pressure">units_pressure</string>
+
     <!-- Location Settings -->
     <string name="pk_location_recent_requests_entry" translatable="false">
         location_recent_requests_entry
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e5d4a1..b947130 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -369,6 +369,125 @@
     <!-- Sound settings screen, title for the option defining the default alarm sound. [CHAR LIMIT=30] -->
     <string name="alarm_ringtone_title">Default alarm sound</string>
 
+    <!-- Units Settings -->
+    <!-- Units Settings title [CHAR LIMIT=10] -->
+    <string name="units_settings">Units</string>
+    <string name="units_distance_and_volume_title">Distance and volume</string>
+    <string name="units_speed_title">Speed</string>
+    <string name="units_fuel_consumption_title">Fuel consumption</string>
+    <string name="units_battery_consumption_title">Energy consumption</string>
+    <string name="units_temperature_title">Temperature</string>
+    <string name="units_pressure_title">Pressure</string>
+
+    <!-- Meter per Second Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_meter_per_sec">Meter per Second</string>
+    <!-- Revolutions per Minute Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_rpm">Revolutions per Minute</string>
+    <!-- Hertz Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_hertz">Hertz</string>
+    <!-- Percentile Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_percentile">Percentile</string>
+    <!-- Millimeter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_millimeter">Millimeter</string>
+    <!-- Meter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_meter">Meter</string>
+    <!-- Kilometer Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_kilometer">Kilometer</string>
+    <!-- Mile Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_mile">Mile</string>
+    <!-- Celsius Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_celsius">Celsius</string>
+    <!-- Fahrenheit Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_fahrenheit">Fahrenheit</string>
+    <!-- Kelvin Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_kelvin">Kelvin</string>
+    <!-- Milliliter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_milliliter">Milliliter</string>
+    <!-- Liter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_liter">Liter</string>
+    <!-- Gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_us_gallon">Gallon</string>
+    <!-- Imperial Gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_imperial_gallon">Imperial Gallon</string>
+    <!-- Nanosecond Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_nano_secs">Nanosecond</string>
+    <!-- Second Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_secs">Second</string>
+    <!-- Year Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_year">Year</string>
+    <!-- Kilopascal Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_kilopascal">Kilopascal</string>
+    <!-- Watt Hour Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_watt_hour">Watt Hour</string>
+    <!-- Milliampere Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_milliampere">Milliampere</string>
+    <!-- Millivolt Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_millivolt">Millivolt</string>
+    <!-- Milliwatt Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_milliwatts">Milliwatt</string>
+    <!-- Ampere hour Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_ampere_hour">Ampere hour</string>
+    <!-- Kilowatt hour Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_kilowatt_hour">Kilowatt hour</string>
+    <!-- Pounds per square inch Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_psi">Pounds per square inch</string>
+    <!-- Miles per hour Unit as it is pronounced [CHAR LIMIT = 50]-->
+    <string name="units_unit_name_miles_per_hour">Miles per hour</string>
+    <!-- Kilometers per hour Unit as it is pronounced [CHAR LIMIT = 50]-->
+    <string name="units_unit_name_kilometers_per_hour">Kilometers per hour</string>
+    <!-- Bar Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_bar">Bar</string>
+    <!-- Degree Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_degrees">Degree</string>
+    <!-- Miles per Kilowatt Unit as it is pronounced [CHAR LIMIT=80]-->
+    <string name="units_unit_name_kilowatt_per_hundred_miles">Kilowatt per hundred Miles</string>
+    <!-- Kilometers per Kilowatt Unit as it is pronounced [CHAR LIMIT=80]-->
+    <string name="units_unit_name_kilowatt_per_hundred_kilometers">Kilowatt per hundred Kilometers</string>
+    <!--Miles per gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_miles_per_gallon_us">Miles per Gallon (US)</string>
+    <!--Miles per gallon Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_miles_per_gallon_uk">Miles per Gallon (UK)</string>
+    <!--Kilometers per liter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_kilometers_per_liter">Kilometers per Liter</string>
+    <!--Kilometers per liter Unit as it is pronounced [CHAR LIMIT=50]-->
+    <string name="units_unit_name_liter_per_hundred_kilometers">Liters per hundred Kilometers</string>
+
+    <!-- Units in abbreviated form -->
+    <string name="units_unit_abbreviation_meter_per_sec" translatable="false">m/s</string>
+    <string name="units_unit_abbreviation_rpm" translatable="false">RPM</string>
+    <string name="units_unit_abbreviation_hertz" translatable="false">Hz</string>
+    <string name="units_unit_abbreviation_percentile" translatable="false">%</string>
+    <string name="units_unit_abbreviation_millimeter" translatable="false">mm</string>
+    <string name="units_unit_abbreviation_meter" translatable="false">m</string>
+    <string name="units_unit_abbreviation_kilometer" translatable="false">km</string>
+    <string name="units_unit_abbreviation_mile" translatable="false">mi</string>
+    <string name="units_unit_abbreviation_celsius" translatable="false">°C</string>
+    <string name="units_unit_abbreviation_fahrenheit" translatable="false">°F</string>
+    <string name="units_unit_abbreviation_kelvin" translatable="false">K</string>
+    <string name="units_unit_abbreviation_milliliter" translatable="false">ml</string>
+    <string name="units_unit_abbreviation_liter" translatable="false">L</string>
+    <string name="units_unit_abbreviation_us_gallon" translatable="false">gal</string>
+    <string name="units_unit_abbreviation_imperial_gallon" translatable="false">IG</string>
+    <string name="units_unit_abbreviation_nano_secs" translatable="false">ns</string>
+    <string name="units_unit_abbreviation_secs" translatable="false">s</string>
+    <string name="units_unit_abbreviation_year" translatable="false">y</string>
+    <string name="units_unit_abbreviation_kilopascal" translatable="false">kPa</string>
+    <string name="units_unit_abbreviation_watt_hour" translatable="false">Wh</string>
+    <string name="units_unit_abbreviation_milliampere" translatable="false">mA</string>
+    <string name="units_unit_abbreviation_millivolt" translatable="false">mV</string>
+    <string name="units_unit_abbreviation_milliwatts" translatable="false">mW</string>
+    <string name="units_unit_abbreviation_ampere_hour" translatable="false">Ah</string>
+    <string name="units_unit_abbreviation_kilowatt_hour" translatable="false">kWh</string>
+    <string name="units_unit_abbreviation_psi" translatable="false">PSI</string>
+    <string name="units_unit_abbreviation_bar" translatable="false">bar</string>
+    <string name="units_unit_abbreviation_degrees" translatable="false">°</string>
+    <string name="units_unit_abbreviation_miles_per_hour" translatable="false">mph</string>
+    <string name="units_unit_abbreviation_kilometers_per_hour" translatable="false">km/h</string>
+    <string name="units_unit_abbreviation_miles_per_gallon_us" translatable="false">mpg (US)</string>
+    <string name="units_unit_abbreviation_miles_per_gallon_uk" translatable="false">mpg (UK)</string>
+    <string name="units_unit_abbreviation_kilometers_per_liter" translatable="false">km/L</string>
+    <string name="units_unit_abbreviation_liters_per_hundred_kilometers" translatable="false">L/100km</string>
+
     <!-- applications --><skip/>
     <!-- Apps and notifications settings title, on main settings screen. If clicked, the user is taken to a settings screen full of apps and notifications settings [CHAR LIMIT=40] -->
     <string name="apps_and_notifications_settings">Apps &amp; notifications</string>
diff --git a/res/xml/homepage_fragment.xml b/res/xml/homepage_fragment.xml
index 75944e5..fa343f6 100644
--- a/res/xml/homepage_fragment.xml
+++ b/res/xml/homepage_fragment.xml
@@ -37,6 +37,11 @@
         android:title="@string/sound_settings"
         settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
     <Preference
+        android:fragment="com.android.car.settings.units.UnitsSettingsFragment"
+        android:icon="@drawable/ic_settings_units"
+        android:key="@string/pk_units_settings_entry"
+        android:title="@string/units_settings"/>
+    <Preference
         android:fragment="com.android.car.settings.network.NetworkAndInternetFragment"
         android:icon="@drawable/ic_settings_wifi"
         android:key="@string/pk_network_and_internet_entry"
diff --git a/res/xml/units_fragment.xml b/res/xml/units_fragment.xml
new file mode 100644
index 0000000..ee17049
--- /dev/null
+++ b/res/xml/units_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/units_settings" >
+    <ListPreference
+        android:key="@string/pk_units_temperature"
+        android:title="@string/units_temperature_title"
+        android:dialogTitle="@string/units_temperature_title"
+        settings:controller="com.android.car.settings.units.UnitsTemperaturePreferenceController"/>
+</PreferenceScreen>
diff --git a/src/com/android/car/settings/units/CarUnitsManager.java b/src/com/android/car/settings/units/CarUnitsManager.java
new file mode 100644
index 0000000..e8d8b69
--- /dev/null
+++ b/src/com/android/car/settings/units/CarUnitsManager.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.VehiclePropertyIds;
+import android.car.VehicleUnit;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.ArraySet;
+
+import com.android.car.settings.common.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Utility to read and write {@link Unit}-related properties in {@link CarPropertyManager}. */
+public class CarUnitsManager {
+    private static final Logger LOG = new Logger(CarUnitsManager.class);
+    private static final int AREA_ID = 0;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                mCarPropertyManager =
+                        (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+                mCarServiceListener.handleServiceConnected(mCarPropertyManager);
+            } catch (CarNotConnectedException e) {
+                LOG.e("Car is not connected!", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mCarServiceListener.handleServiceDisconnected();
+        }
+    };
+
+    private Context mContext;
+    private Car mCar;
+    private CarPropertyManager mCarPropertyManager;
+    private OnCarServiceListener mCarServiceListener;
+
+    public CarUnitsManager(Context context) {
+        mContext = context;
+        mCar = Car.createCar(mContext, mServiceConnection);
+    }
+
+    /**
+     * Registers {@link OnCarServiceListener} as a Callback for when connection to {@link Car} has
+     * been established.
+     */
+    public void registerCarServiceListener(OnCarServiceListener listener) {
+        mCarServiceListener = listener;
+    }
+
+    /**
+     * Unregisters {@link OnCarServiceListener} as a Callback for when connection to {@link Car} has
+     * been terminated.
+     */
+    public void unregisterCarServiceListener() {
+        mCarServiceListener = null;
+    }
+
+    protected void connect() {
+        mCar.connect();
+    }
+
+    protected void disconnect() {
+        mCar.disconnect();
+    }
+
+    protected boolean isPropertyAvailable(int propertyId) {
+        Integer intProperty = null;
+
+        try {
+            intProperty = mCarPropertyManager.getIntProperty(propertyId, AREA_ID);
+        } catch (CarNotConnectedException e) {
+            LOG.e("Property is unavailable because Car is not connected.");
+        }
+
+        return intProperty != null && intProperty != VehicleUnit.SHOULD_NOT_USE;
+    }
+
+    protected Unit[] getUnitsSupportedByProperty(int propertyId) {
+        ArraySet<Integer> propertyIdSet = new ArraySet<Integer>();
+        propertyIdSet.add(propertyId);
+        List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList(propertyIdSet);
+        List<Integer> availableUnitsId = new ArrayList<Integer>();
+        List<Unit> units = new ArrayList<Unit>();
+
+        if (configs.get(0) == null) {
+            return null;
+        }
+
+        availableUnitsId = configs.get(0).getConfigArray();
+
+        Unit[] result = new Unit[availableUnitsId.size()];
+        for (int unitId : availableUnitsId) {
+            if (UnitsMap.MAP.get(unitId) != null) {
+                Unit unit = UnitsMap.MAP.get(unitId);
+                units.add(unit);
+            }
+        }
+        for (int i = 0; i < result.length; i++) {
+            int unitId = availableUnitsId.get(i);
+            if (UnitsMap.MAP.get(unitId) != null) {
+                Unit unit = UnitsMap.MAP.get(unitId);
+                result[i] = unit;
+            }
+        }
+        return result;
+    }
+
+    protected Unit getUnitUsedByProperty(int propertyId) {
+        try {
+            int unitId = mCarPropertyManager.getIntProperty(propertyId, AREA_ID);
+            if (UnitsMap.MAP.get(unitId) != null) {
+                return UnitsMap.MAP.get(unitId);
+            } else {
+                return null;
+            }
+        } catch (CarNotConnectedException e) {
+            LOG.e("CarPropertyManager cannot get property because Car is not connected.");
+            return null;
+        }
+    }
+
+    protected void setUnitUsedByProperty(int propertyId, int unitId) {
+        try {
+            mCarPropertyManager.setIntProperty(propertyId, AREA_ID, unitId);
+        } catch (CarNotConnectedException e) {
+            LOG.e("CarPropertyManager cannot set property because Car is not connected.");
+        }
+    }
+
+    /**
+     * Returns a boolean that indicates whether the unit is expressed in distance per volume (true)
+     * or volume per distance (false) for fuel consumption. Note that only distance over volume
+     * format is supported when Mile and Gallon (both US and UK) units are used.
+     */
+    protected boolean isDistanceOverVolume() {
+        try {
+            return mCarPropertyManager.getBooleanProperty(
+                    VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME, AREA_ID);
+        } catch (CarNotConnectedException e) {
+            return true; // Defaults to True.
+        }
+    }
+
+    /** Defines callbacks that listen to {@link Car} service-related events. */
+    public interface OnCarServiceListener {
+        /**
+         * Callback to be run when {@link Car} service is connected and {@link
+         * CarPropertyManager} becomes available.
+         */
+        void handleServiceConnected(CarPropertyManager carPropertyManager);
+
+        /** Callback to be run when {@link Car} service is disconnected. */
+        void handleServiceDisconnected();
+    }
+}
diff --git a/src/com/android/car/settings/units/Unit.java b/src/com/android/car/settings/units/Unit.java
new file mode 100644
index 0000000..bc5e9b7
--- /dev/null
+++ b/src/com/android/car/settings/units/Unit.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.annotation.StringRes;
+import android.car.VehicleUnit;
+
+/**
+ * Contains properties and methods associated with each unit defined in {@link
+ * android.car.VehicleUnit}.
+ */
+public class Unit {
+    private final int mId;
+    private final int mAbbreviationResId;
+    private final int mNameResId;
+
+    Unit(@VehicleUnit.Enum int unitId, @StringRes int unitAbbreviationResId,
+            @StringRes int unitNameResId) {
+        mId = unitId;
+        mAbbreviationResId = unitAbbreviationResId;
+        mNameResId = unitNameResId;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public int getAbbreviationResId() {
+        return mAbbreviationResId;
+    }
+
+    public int getNameResId() {
+        return mNameResId;
+    }
+}
diff --git a/src/com/android/car/settings/units/UnitsBasePreferenceController.java b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
new file mode 100644
index 0000000..83567fd
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.car.settings.units;
+
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.Context;
+
+import androidx.annotation.CallSuper;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Shared business logic for preference controllers related to Units.
+ */
+public abstract class UnitsBasePreferenceController extends PreferenceController<ListPreference> {
+
+    @VisibleForTesting
+    protected final CarUnitsManager.OnCarServiceListener mOnCarServiceListener =
+            new CarUnitsManager.OnCarServiceListener() {
+                @Override
+                public void handleServiceConnected(CarPropertyManager carPropertyManager) {
+                    try {
+                        if (carPropertyManager != null) {
+                            carPropertyManager.registerCallback(mCarPropertyEventCallback,
+                                    getPropertyId(), CarPropertyManager.SENSOR_RATE_ONCHANGE);
+                        }
+                        mSupportedUnits = mCarUnitsManager.getUnitsSupportedByProperty(
+                                getPropertyId());
+                        if (mSupportedUnits != null && mSupportedUnits.length > 0) {
+                            // first element in the config array is the default Unit per VHAL spec.
+                            mDefaultUnit = mSupportedUnits[0];
+                            getPreference().setEntries(getEntriesOfSupportedUnits());
+                            getPreference().setEntryValues(getIdsOfSupportedUnits());
+                            getPreference().setValue(
+                                    Integer.toString(getUnitUsedByThisProperty().getId()));
+                            refreshUi();
+                        }
+
+                        mIsCarUnitsManagerStarted = true;
+                    } catch (CarNotConnectedException e) {
+                    }
+                }
+
+                @Override
+                public void handleServiceDisconnected() {
+                    mIsCarUnitsManagerStarted = false;
+                }
+            };
+
+    @VisibleForTesting
+    protected final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+            new CarPropertyManager.CarPropertyEventCallback() {
+                @Override
+                public void onChangeEvent(CarPropertyValue value) {
+                    if (value != null && value.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
+                        mUnitBeingUsed = UnitsMap.MAP.get(value.getValue());
+                        refreshUi();
+                    }
+                }
+
+                @Override
+                public void onErrorEvent(int propId, int zone) {
+                }
+            };
+
+    private Unit[] mSupportedUnits;
+    private Unit mUnitBeingUsed;
+    private Unit mDefaultUnit;
+    private boolean mIsCarUnitsManagerStarted = false;
+    private CarUnitsManager mCarUnitsManager;
+
+    public UnitsBasePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    @CallSuper
+    protected void onCreateInternal() {
+        super.onCreateInternal();
+        mCarUnitsManager = new CarUnitsManager(getContext());
+        mCarUnitsManager.connect();
+        mCarUnitsManager.registerCarServiceListener(mOnCarServiceListener);
+    }
+
+    @Override
+    @CallSuper
+    protected void onDestroyInternal() {
+        super.onDestroyInternal();
+        mCarUnitsManager.disconnect();
+        mCarUnitsManager.unregisterCarServiceListener();
+    }
+
+    @Override
+    @CallSuper
+    protected void updateState(ListPreference preference) {
+        if (mIsCarUnitsManagerStarted && mUnitBeingUsed != null) {
+            preference.setSummary(generateSummaryFromUnit(mUnitBeingUsed));
+            preference.setValue(Integer.toString(mUnitBeingUsed.getId()));
+        }
+    }
+
+    @Override
+    @CallSuper
+    public boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+        int unitId = Integer.parseInt((String) newValue);
+        mCarUnitsManager.setUnitUsedByProperty(getPropertyId(), unitId);
+        return true;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return mSupportedUnits != null && mSupportedUnits.length > 0
+                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    protected abstract int getPropertyId();
+
+    protected String[] getEntriesOfSupportedUnits() {
+        String[] names = new String[mSupportedUnits.length];
+        for (int i = 0; i < names.length; i++) {
+            Unit unit = mSupportedUnits[i];
+            names[i] = generateEntryStringFromUnit(unit);
+        }
+        return names;
+    }
+
+    protected String generateSummaryFromUnit(Unit unit) {
+        return getContext().getString(unit.getAbbreviationResId());
+    }
+
+    protected String generateEntryStringFromUnit(Unit unit) {
+        return getContext().getString(unit.getAbbreviationResId()) + " - "
+                + getContext().getString(unit.getNameResId());
+    }
+
+    protected String[] getIdsOfSupportedUnits() {
+        String[] ids = new String[mSupportedUnits.length];
+        for (int i = 0; i < ids.length; i++) {
+            ids[i] = Integer.toString(mSupportedUnits[i].getId());
+        }
+        return ids;
+    }
+
+    protected CarUnitsManager getCarUnitsManager() {
+        return mCarUnitsManager;
+    }
+
+    private Unit getUnitUsedByThisProperty() {
+        Unit savedUnit = mCarUnitsManager.getUnitUsedByProperty(getPropertyId());
+        if (savedUnit == null) {
+            return mDefaultUnit;
+        }
+        return savedUnit;
+    }
+
+}
diff --git a/src/com/android/car/settings/units/UnitsMap.java b/src/com/android/car/settings/units/UnitsMap.java
new file mode 100644
index 0000000..12264de
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsMap.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.VehicleUnit;
+
+import com.android.car.settings.R;
+
+import java.util.HashMap;
+
+/**
+ * Contains {@link Unit} instances for all units defined in {@link VehicleUnit}. This mapping is
+ * safe because OEMs cannot define their own VehicleUnit.
+ */
+public final class UnitsMap {
+    protected static final Unit METER_PER_SEC = new Unit(VehicleUnit.METER_PER_SEC,
+            R.string.units_unit_abbreviation_meter_per_sec, R.string.units_unit_name_meter_per_sec);
+    protected static final Unit RPM = new Unit(VehicleUnit.RPM,
+            R.string.units_unit_abbreviation_rpm, R.string.units_unit_name_rpm);
+    protected static final Unit HERTZ = new Unit(VehicleUnit.HERTZ,
+            R.string.units_unit_abbreviation_hertz, R.string.units_unit_name_hertz);
+    protected static final Unit PERCENTILE = new Unit(VehicleUnit.PERCENTILE,
+            R.string.units_unit_abbreviation_percentile, R.string.units_unit_name_percentile);
+    protected static final Unit MILLIMETER = new Unit(VehicleUnit.MILLIMETER,
+            R.string.units_unit_abbreviation_millimeter, R.string.units_unit_name_millimeter);
+    protected static final Unit METER = new Unit(VehicleUnit.METER,
+            R.string.units_unit_abbreviation_meter, R.string.units_unit_name_meter);
+    protected static final Unit KILOMETER = new Unit(VehicleUnit.KILOMETER,
+            R.string.units_unit_abbreviation_kilometer, R.string.units_unit_name_kilometer);
+    protected static final Unit MILE = new Unit(VehicleUnit.MILE,
+            R.string.units_unit_abbreviation_mile, R.string.units_unit_name_mile);
+    protected static final Unit CELSIUS = new Unit(VehicleUnit.CELSIUS,
+            R.string.units_unit_abbreviation_celsius, R.string.units_unit_name_celsius);
+    protected static final Unit FAHRENHEIT = new Unit(VehicleUnit.FAHRENHEIT,
+            R.string.units_unit_abbreviation_fahrenheit, R.string.units_unit_name_fahrenheit);
+    protected static final Unit KELVIN = new Unit(VehicleUnit.KELVIN,
+            R.string.units_unit_abbreviation_kelvin, R.string.units_unit_name_kelvin);
+    protected static final Unit MILLILITER = new Unit(VehicleUnit.MILLILITER,
+            R.string.units_unit_abbreviation_milliliter, R.string.units_unit_name_milliliter);
+    protected static final Unit LITER = new Unit(VehicleUnit.LITER,
+            R.string.units_unit_abbreviation_liter, R.string.units_unit_name_liter);
+    protected static final Unit US_GALLON = new Unit(VehicleUnit.US_GALLON,
+            R.string.units_unit_abbreviation_us_gallon, R.string.units_unit_name_us_gallon);
+    protected static final Unit IMPERIAL_GALLON = new Unit(VehicleUnit.IMPERIAL_GALLON,
+            R.string.units_unit_abbreviation_imperial_gallon,
+            R.string.units_unit_name_imperial_gallon);
+    protected static final Unit NANO_SECS = new Unit(VehicleUnit.NANO_SECS,
+            R.string.units_unit_abbreviation_nano_secs, R.string.units_unit_name_nano_secs);
+    protected static final Unit SECS = new Unit(VehicleUnit.SECS,
+            R.string.units_unit_abbreviation_secs, R.string.units_unit_name_secs);
+    protected static final Unit YEAR = new Unit(VehicleUnit.YEAR,
+            R.string.units_unit_abbreviation_year, R.string.units_unit_name_year);
+    protected static final Unit KILOPASCAL = new Unit(VehicleUnit.KILOPASCAL,
+            R.string.units_unit_abbreviation_kilopascal, R.string.units_unit_name_kilopascal);
+    protected static final Unit WATT_HOUR = new Unit(VehicleUnit.WATT_HOUR,
+            R.string.units_unit_abbreviation_watt_hour, R.string.units_unit_name_watt_hour);
+    protected static final Unit MILLIAMPERE = new Unit(VehicleUnit.MILLIAMPERE,
+            R.string.units_unit_abbreviation_milliampere, R.string.units_unit_name_milliampere);
+    protected static final Unit MILLIVOLT = new Unit(VehicleUnit.MILLIVOLT,
+            R.string.units_unit_abbreviation_millivolt, R.string.units_unit_name_millivolt);
+    protected static final Unit MILLIWATTS = new Unit(VehicleUnit.MILLIWATTS,
+            R.string.units_unit_abbreviation_milliwatts, R.string.units_unit_name_milliwatts);
+    protected static final Unit AMPERE_HOURS = new Unit(VehicleUnit.AMPERE_HOURS,
+            R.string.units_unit_abbreviation_ampere_hour, R.string.units_unit_name_ampere_hour);
+    protected static final Unit KILOWATT_HOUR = new Unit(VehicleUnit.KILOWATT_HOUR,
+            R.string.units_unit_abbreviation_kilowatt_hour, R.string.units_unit_name_kilowatt_hour);
+    protected static final Unit PSI = new Unit(VehicleUnit.PSI,
+            R.string.units_unit_abbreviation_psi, R.string.units_unit_name_psi);
+    protected static final Unit BAR = new Unit(VehicleUnit.BAR,
+            R.string.units_unit_abbreviation_bar, R.string.units_unit_name_bar);
+    protected static final Unit DEGREES = new Unit(VehicleUnit.DEGREES,
+            R.string.units_unit_abbreviation_degrees, R.string.units_unit_name_degrees);
+    protected static final Unit MILES_PER_HOUR = new Unit(VehicleUnit.MILES_PER_HOUR,
+            R.string.units_unit_abbreviation_miles_per_hour,
+            R.string.units_unit_name_miles_per_hour);
+    protected static final Unit KILOMETERS_PER_HOUR = new Unit(VehicleUnit.KILOMETERS_PER_HOUR,
+            R.string.units_unit_abbreviation_kilometers_per_hour,
+            R.string.units_unit_name_kilometers_per_hour);
+
+    public static final HashMap<Integer, Unit> MAP = createMap();
+
+    private static HashMap<Integer, Unit> createMap() {
+        HashMap<Integer, Unit> map = new HashMap();
+        map.put(VehicleUnit.METER_PER_SEC, METER_PER_SEC);
+        map.put(VehicleUnit.RPM, RPM);
+        map.put(VehicleUnit.HERTZ, HERTZ);
+        map.put(VehicleUnit.PERCENTILE, PERCENTILE);
+        map.put(VehicleUnit.MILLIMETER, MILLIMETER);
+        map.put(VehicleUnit.METER, METER);
+        map.put(VehicleUnit.KILOMETER, KILOMETER);
+        map.put(VehicleUnit.MILE, MILE);
+        map.put(VehicleUnit.CELSIUS, CELSIUS);
+        map.put(VehicleUnit.FAHRENHEIT, FAHRENHEIT);
+        map.put(VehicleUnit.KELVIN, KELVIN);
+        map.put(VehicleUnit.MILLILITER, MILLILITER);
+        map.put(VehicleUnit.LITER, LITER);
+        map.put(VehicleUnit.US_GALLON, US_GALLON);
+        map.put(VehicleUnit.IMPERIAL_GALLON, IMPERIAL_GALLON);
+        map.put(VehicleUnit.NANO_SECS, NANO_SECS);
+        map.put(VehicleUnit.SECS, SECS);
+        map.put(VehicleUnit.YEAR, YEAR);
+        map.put(VehicleUnit.KILOPASCAL, KILOPASCAL);
+        map.put(VehicleUnit.WATT_HOUR, WATT_HOUR);
+        map.put(VehicleUnit.MILLIAMPERE, MILLIAMPERE);
+        map.put(VehicleUnit.MILLIVOLT, MILLIVOLT);
+        map.put(VehicleUnit.MILLIWATTS, MILLIWATTS);
+        map.put(VehicleUnit.AMPERE_HOURS, AMPERE_HOURS);
+        map.put(VehicleUnit.KILOWATT_HOUR, KILOWATT_HOUR);
+        map.put(VehicleUnit.PSI, PSI);
+        map.put(VehicleUnit.BAR, BAR);
+        map.put(VehicleUnit.DEGREES, DEGREES);
+        map.put(VehicleUnit.MILES_PER_HOUR, MILES_PER_HOUR);
+        map.put(VehicleUnit.KILOMETERS_PER_HOUR, KILOMETERS_PER_HOUR);
+
+        return map;
+    }
+
+    private UnitsMap() {
+    }
+}
diff --git a/src/com/android/car/settings/units/UnitsSettingsFragment.java b/src/com/android/car/settings/units/UnitsSettingsFragment.java
new file mode 100644
index 0000000..7c0bed9
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsSettingsFragment.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import androidx.annotation.LayoutRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Fragment to host Units-related preferences. */
+public class UnitsSettingsFragment extends SettingsFragment {
+
+    @Override
+    @LayoutRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.units_fragment;
+    }
+
+}
diff --git a/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java b/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java
new file mode 100644
index 0000000..fa1968a
--- /dev/null
+++ b/src/com/android/car/settings/units/UnitsTemperaturePreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import android.car.VehiclePropertyIds;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.common.FragmentController;
+
+/** Controls {@link Unit} used for HVAC Temperature Display. */
+public class UnitsTemperaturePreferenceController extends UnitsBasePreferenceController {
+
+    public UnitsTemperaturePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<ListPreference> getPreferenceType() {
+        return ListPreference.class;
+    }
+
+    @Override
+    protected int getPropertyId() {
+        return VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java
new file mode 100644
index 0000000..91ed48e
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowCarUnitsManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.testutils;
+
+import com.android.car.settings.units.CarUnitsManager;
+import com.android.car.settings.units.Unit;
+import com.android.car.settings.units.UnitsMap;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.HashMap;
+
+/**
+ * Shadow class for {@link CarUnitsManager}.
+ */
+@Implements(CarUnitsManager.class)
+public class ShadowCarUnitsManager {
+    private static boolean sConnected = false;
+    private static CarUnitsManager.OnCarServiceListener sListener;
+    private static HashMap<Integer, Unit[]> sSupportedUnits = new HashMap<>();
+    private static HashMap<Integer, Unit> sUnitsBeingUsed = new HashMap<>();
+
+    @Implementation
+    protected void connect() {
+        sConnected = true;
+    }
+
+    @Implementation
+    protected void disconnect() {
+        sConnected = false;
+    }
+
+    @Implementation
+    protected static Unit[] getUnitsSupportedByProperty(int propertyId) {
+        return sSupportedUnits.get(propertyId);
+    }
+
+    @Implementation
+    protected static Unit getUnitUsedByProperty(int propertyId) {
+        return sUnitsBeingUsed.get(propertyId);
+    }
+
+    @Implementation
+    protected static void setUnitUsedByProperty(int propertyId, int unitId) {
+        sUnitsBeingUsed.put(propertyId, UnitsMap.MAP.get(unitId));
+    }
+
+    @Implementation
+    protected static void registerCarServiceListener(
+            CarUnitsManager.OnCarServiceListener listener) {
+        sListener = listener;
+    }
+
+    @Implementation
+    protected static void unregisterCarServiceListener() {
+        sListener = null;
+    }
+
+    @Resetter
+    public static void reset() {
+        sConnected = false;
+        sListener = null;
+        sSupportedUnits = new HashMap<>();
+        sUnitsBeingUsed = new HashMap<>();
+    }
+
+    public static void setUnitsSupportedByProperty(int propertyId, Unit[] units) {
+        sSupportedUnits.put(propertyId, units);
+    }
+
+    public static boolean isConnected() {
+        return sConnected;
+    }
+
+    public static CarUnitsManager.OnCarServiceListener getListener() {
+        return sListener;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
new file mode 100644
index 0000000..becafe8
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.units;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCar;
+import com.android.car.settings.testutils.ShadowCarUnitsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCar.class, ShadowCarUnitsManager.class})
+public class UnitsBasePreferenceControllerTest {
+
+    private static final int TEST_PROPERTY_ID = -1;
+    private static final Unit[] AVAILABLE_UNITS =
+            {UnitsMap.YEAR, UnitsMap.SECS, UnitsMap.NANO_SECS};
+    private static final Unit DEFAULT_UNIT = UnitsMap.YEAR;
+
+    private Context mContext;
+    private ListPreference mPreference;
+    private PreferenceControllerTestHelper<TestUnitsBasePreferenceController> mControllerHelper;
+    private TestUnitsBasePreferenceController mController;
+    private CarUnitsManager mCarUnitsManager;
+
+    @Mock
+    private CarPropertyManager mCarPropertyManager;
+    @Mock
+    private CarPropertyValue mCarPropertyValue;
+
+    private static class TestUnitsBasePreferenceController extends UnitsBasePreferenceController {
+
+        private TestUnitsBasePreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+            super(context, preferenceKey, fragmentController, uxRestrictions);
+        }
+
+        @Override
+        protected int getPropertyId() {
+            return TEST_PROPERTY_ID;
+        }
+
+
+        @Override
+        protected Class<ListPreference> getPreferenceType() {
+            return ListPreference.class;
+        }
+    }
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        ShadowCarUnitsManager.setUnitsSupportedByProperty(TEST_PROPERTY_ID, AVAILABLE_UNITS);
+        mCarUnitsManager = new CarUnitsManager(mContext);
+        mCarUnitsManager.setUnitUsedByProperty(TEST_PROPERTY_ID, DEFAULT_UNIT.getId());
+        mPreference = new ListPreference(mContext);
+        mControllerHelper = new PreferenceControllerTestHelper<>(
+                mContext, TestUnitsBasePreferenceController.class, mPreference);
+        mController = mControllerHelper.getController();
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCarUnitsManager.reset();
+    }
+
+
+    @Test
+    public void onCreate_connectsCarUnitsManager() {
+        assertThat(ShadowCarUnitsManager.isConnected()).isTrue();
+    }
+
+    @Test
+    public void onCreate_registersCarServiceListener() {
+        assertThat(ShadowCarUnitsManager.getListener())
+                .isEqualTo(mController.mOnCarServiceListener);
+    }
+
+    @Test
+    public void onCreate_preferenceIsConditionallyUnavailable() {
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_availableUnitsExist_preferenceIsAvailable() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(PreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_noAvailableUnits_preferenceIsConditionallyUnavailable() {
+        ShadowCarUnitsManager.setUnitsSupportedByProperty(TEST_PROPERTY_ID, null);
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsEntriesOfSupportedUnits() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        CharSequence[] expectedEntries = mController.getEntriesOfSupportedUnits();
+
+        assertThat(mPreference.getEntries()).isEqualTo(expectedEntries);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsSupportedUnitsIdsAsEntryValues() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        CharSequence[] expectedEntryValues = mController.getIdsOfSupportedUnits();
+
+        assertThat(mPreference.getEntryValues()).isEqualTo(expectedEntryValues);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsUnitBeingUsedAsPreferenceValue() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        String expectedValue = Integer.toString(DEFAULT_UNIT.getId());
+
+        assertThat(mPreference.getValue()).isEqualTo(expectedValue);
+    }
+
+    @Test
+    public void onPreferenceChanged_runsSetUnitUsedByPropertyWithNewUnit() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        mController.handlePreferenceChanged(mPreference, Integer.toString(UnitsMap.SECS.getId()));
+
+        assertThat(mCarUnitsManager.getUnitUsedByProperty(TEST_PROPERTY_ID))
+                .isEqualTo(UnitsMap.SECS);
+    }
+
+    @Test
+    public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitIdAsValue() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        Unit newUnit = UnitsMap.SECS;
+        when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+        when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+        mController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+        assertThat(mPreference.getValue()).isEqualTo(Integer.toString(newUnit.getId()));
+    }
+
+    @Test
+    public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitAbbreviationAsSummary() {
+        mController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        Unit newUnit = UnitsMap.SECS;
+        when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+        when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+        mController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(mContext.getString(newUnit.getAbbreviationResId()));
+    }
+
+    @Test
+    public void onDestroy_disconnectsCarUnitsManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        assertThat(ShadowCarUnitsManager.isConnected()).isFalse();
+    }
+
+    @Test
+    public void onDestroy_unregistersCarServiceListener() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        assertThat(ShadowCarUnitsManager.getListener()).isNull();
+    }
+}