Remove deprecated use of createCar() API

Bug: 189221880
Test: manual, atest CarSettingsUnitTests
Change-Id: I4e36fe11c00dfd5dd91776cbee2355f41ad6e13d
diff --git a/src/com/android/car/settings/common/BaseCarSettingsActivity.java b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
index b9840f8..7b9637d 100644
--- a/src/com/android/car/settings/common/BaseCarSettingsActivity.java
+++ b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
@@ -139,11 +139,6 @@
         populateMetaData();
         setContentView(R.layout.car_setting_activity);
         mFragmentContainer = findViewById(R.id.fragment_container);
-        if (mUxRestrictionsHelper == null) {
-            mUxRestrictionsHelper = new CarUxRestrictionsHelper(/* context= */ this, /* listener= */
-                    this);
-        }
-        mUxRestrictionsHelper.start();
 
         // We do this so that the insets are not automatically sent to the fragments.
         // The fragments have their own insets handled by the installBaseLayoutAround() method.
@@ -159,7 +154,8 @@
         } else if (!mIsSinglePane) {
             updateMiniToolbarState();
         }
-
+        mUxRestrictionsHelper = new CarUxRestrictionsHelper(/* context= */ this, /* listener= */
+                this);
         setUpFocusChangeListener(true);
     }
 
@@ -173,7 +169,7 @@
     public void onDestroy() {
         setUpFocusChangeListener(false);
         removeGlobalLayoutListener();
-        mUxRestrictionsHelper.stop();
+        mUxRestrictionsHelper.destroy();
         mUxRestrictionsHelper = null;
         super.onDestroy();
     }
@@ -338,6 +334,9 @@
     }
 
     private void updateBlockingView(@Nullable Fragment currentFragment) {
+        if (mRestrictedMessage == null) {
+            return;
+        }
         if (currentFragment instanceof BaseFragment
                 && !((BaseFragment) currentFragment).canBeShown(mCarUxRestrictions)) {
             mRestrictedMessage.setVisibility(View.VISIBLE);
diff --git a/src/com/android/car/settings/common/CarUxRestrictionsHelper.java b/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
index e683661..4c31025 100644
--- a/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
+++ b/src/com/android/car/settings/common/CarUxRestrictionsHelper.java
@@ -17,13 +17,9 @@
 
 import android.app.Activity;
 import android.car.Car;
-import android.car.CarNotConnectedException;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsManager;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
 
 import androidx.annotation.Nullable;
 
@@ -47,42 +43,34 @@
             throw new IllegalArgumentException("Listener cannot be null.");
         }
         mListener = listener;
-        mCar = Car.createCar(context, mServiceConnection);
-    };
-
-    /**
-     * Starts monitoring any changes in {@link CarUxRestrictions}.
-     *
-     * <p>This method can be called from {@code Activity}'s {@link Activity#onStart()}, or at the
-     * time of construction.
-     *
-     * <p>This method must be accompanied with a matching {@link #stop()} to avoid leak.
-     */
-    public void start() {
-        try {
-            if (mCar != null && !mCar.isConnected()) {
-                mCar.connect();
-            }
-        } catch (IllegalStateException e) {
-            // Do nothing.
-            LOG.w("start(); cannot connect to Car");
+        mCar = Car.createCar(context);
+        mCarUxRestrictionsManager = (CarUxRestrictionsManager)
+                mCar.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+        if (mCarUxRestrictionsManager != null) {
+            mCarUxRestrictionsManager.registerListener(mListener);
+            mListener.onUxRestrictionsChanged(
+                    mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
         }
     }
 
     /**
      * Stops monitoring any changes in {@link CarUxRestrictions}.
      *
-     * <p>This method should be called from {@code Activity}'s {@link Activity#onStop()}, or at the
-     * time of this adapter being discarded.
+     * <p>This method should be called from {@code Activity}'s {@link Activity#onDestroy()}, or at
+     * the time of this adapter being discarded.
      */
-    public void stop() {
+    public void destroy() {
         try {
             if (mCar != null && mCar.isConnected()) {
                 mCar.disconnect();
             }
         } catch (IllegalStateException e) {
             // Do nothing.
-            LOG.w("stop(); cannot disconnect from Car");
+            LOG.w("destroy(); cannot disconnect from Car");
+        }
+        if (mCarUxRestrictionsManager != null) {
+            mCarUxRestrictionsManager.unregisterListener();
+            mCarUxRestrictionsManager = null;
         }
     }
 
@@ -94,30 +82,4 @@
                 & CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP)
                 == CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP;
     }
-
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mCarUxRestrictionsManager = (CarUxRestrictionsManager)
-                        mCar.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
-                mCarUxRestrictionsManager.registerListener(mListener);
-
-                mListener.onUxRestrictionsChanged(
-                        mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
-            } catch (CarNotConnectedException e) {
-                e.printStackTrace();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            try {
-                mCarUxRestrictionsManager.unregisterListener();
-                mCarUxRestrictionsManager = null;
-            } catch (CarNotConnectedException e) {
-                e.printStackTrace();
-            }
-        }
-    };
 }
diff --git a/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java b/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java
index 5470825..5ea34a5 100644
--- a/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java
+++ b/src/com/android/car/settings/sound/VolumeSettingsPreferenceController.java
@@ -22,12 +22,9 @@
 import android.car.CarNotConnectedException;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.media.CarAudioManager;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.util.SparseArray;
 
@@ -99,48 +96,42 @@
                 }
             };
 
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
-                int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
-                cleanUpVolumePreferences();
-                // Populates volume slider items from volume groups to UI.
-                for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
-                    VolumeItem volumeItem = getVolumeItemForUsages(
-                            mCarAudioManager.getUsagesForVolumeGroupId(groupId));
-                    SeekBarPreference volumePreference = createVolumeSeekBarPreference(
-                            groupId, volumeItem.getUsage(), volumeItem.getIcon(),
-                            volumeItem.getTitle());
-                    mVolumePreferences.add(volumePreference);
-                }
-                mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
-
-                refreshUi();
-            } catch (CarNotConnectedException e) {
-                LOG.e("Car is not connected!", e);
-            }
-        }
-
-        /** Cleanup audio related fields when car is disconnected. */
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            cleanupAudioManager();
-        }
-    };
-
     private Car mCar;
     private CarAudioManager mCarAudioManager;
 
     public VolumeSettingsPreferenceController(Context context, String preferenceKey,
             FragmentController fragmentController,
             CarUxRestrictions uxRestrictions) {
+        this(context, preferenceKey, fragmentController, uxRestrictions, Car.createCar(context),
+                new VolumeSettingsRingtoneManager(context));
+    }
+
+    @VisibleForTesting
+    VolumeSettingsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController,
+            CarUxRestrictions uxRestrictions, Car car,
+            VolumeSettingsRingtoneManager ringtoneManager) {
         super(context, preferenceKey, fragmentController, uxRestrictions);
-        mCar = Car.createCar(getContext(), mServiceConnection);
+        mCar = car;
+        mRingtoneManager = ringtoneManager;
         mVolumeItems = VolumeItemParser.loadAudioUsageItems(context, carVolumeItemsXml());
-        mRingtoneManager = new VolumeSettingsRingtoneManager(getContext());
         mUiHandler = new Handler(Looper.getMainLooper());
+
+        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+        if (mCarAudioManager != null) {
+            int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+            cleanUpVolumePreferences();
+            // Populates volume slider items from volume groups to UI.
+            for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+                VolumeItem volumeItem = getVolumeItemForUsages(
+                        mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+                SeekBarPreference volumePreference = createVolumeSeekBarPreference(
+                        groupId, volumeItem.getUsage(), volumeItem.getIcon(),
+                        volumeItem.getTitle());
+                mVolumePreferences.add(volumePreference);
+            }
+            mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
+        }
     }
 
     @Override
@@ -148,16 +139,11 @@
         return PreferenceGroup.class;
     }
 
-    /** Connect to car on create. */
-    @Override
-    protected void onCreateInternal() {
-        mCar.connect();
-    }
-
     /** Disconnect from car on destroy. */
     @Override
     protected void onDestroyInternal() {
         mCar.disconnect();
+        cleanupAudioManager();
     }
 
     @Override
diff --git a/src/com/android/car/settings/units/CarUnitsManager.java b/src/com/android/car/settings/units/CarUnitsManager.java
index 112faf3..af60a99 100644
--- a/src/com/android/car/settings/units/CarUnitsManager.java
+++ b/src/com/android/car/settings/units/CarUnitsManager.java
@@ -22,10 +22,7 @@
 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;
@@ -38,24 +35,6 @@
     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;
@@ -63,7 +42,9 @@
 
     public CarUnitsManager(Context context) {
         mContext = context;
-        mCar = Car.createCar(mContext, mServiceConnection);
+        mCar = Car.createCar(mContext);
+        mCarPropertyManager =
+                (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
     }
 
     /**
@@ -72,6 +53,7 @@
      */
     public void registerCarServiceListener(OnCarServiceListener listener) {
         mCarServiceListener = listener;
+        mCarServiceListener.handleServiceConnected(mCarPropertyManager);
     }
 
     /**
@@ -82,12 +64,11 @@
         mCarServiceListener = null;
     }
 
-    protected void connect() {
-        mCar.connect();
-    }
-
     protected void disconnect() {
         mCar.disconnect();
+        if (mCarServiceListener != null) {
+            mCarServiceListener.handleServiceDisconnected();
+        }
     }
 
     protected boolean isPropertyAvailable(int propertyId) {
diff --git a/src/com/android/car/settings/units/UnitsBasePreferenceController.java b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
index 0563a73..1822326 100644
--- a/src/com/android/car/settings/units/UnitsBasePreferenceController.java
+++ b/src/com/android/car/settings/units/UnitsBasePreferenceController.java
@@ -102,8 +102,7 @@
     @CallSuper
     protected void onCreateInternal() {
         super.onCreateInternal();
-        mCarUnitsManager = new CarUnitsManager(getContext());
-        mCarUnitsManager.connect();
+        mCarUnitsManager = createCarUnitsManager();
         mCarUnitsManager.registerCarServiceListener(mOnCarServiceListener);
     }
 
@@ -173,6 +172,11 @@
         return mCarUnitsManager;
     }
 
+    @VisibleForTesting
+    CarUnitsManager createCarUnitsManager() {
+        return new CarUnitsManager(getContext());
+    }
+
     private Unit getUnitUsedByThisProperty() {
         Unit savedUnit = mCarUnitsManager.getUnitUsedByProperty(getPropertyId());
         if (savedUnit == null) {
diff --git a/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
deleted file mode 100644
index 5813c0d..0000000
--- a/tests/robotests/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2018 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.sound;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.drivingstate.CarUxRestrictions;
-import android.car.media.CarAudioManager;
-import android.content.Context;
-import android.media.Ringtone;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.preference.PreferenceGroup;
-
-import com.android.car.settings.R;
-import com.android.car.settings.common.FragmentController;
-import com.android.car.settings.common.LogicalPreferenceGroup;
-import com.android.car.settings.common.PreferenceControllerTestHelper;
-import com.android.car.settings.common.SeekBarPreference;
-import com.android.car.settings.testutils.ShadowCar;
-import com.android.car.settings.testutils.ShadowRingtoneManager;
-
-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.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowRingtoneManager.class})
-public class VolumeSettingsPreferenceControllerTest {
-
-    private static final int ZONE_ID = 1;
-    private static final int GROUP_ID = 0;
-    private static final int TEST_MIN_VOLUME = 0;
-    private static final int TEST_VOLUME = 40;
-    private static final int TEST_NEW_VOLUME = 80;
-    private static final int TEST_MAX_VOLUME = 100;
-
-    private PreferenceControllerTestHelper<TestVolumeSettingsPreferenceController>
-            mPreferenceControllerHelper;
-    private TestVolumeSettingsPreferenceController mController;
-    private PreferenceGroup mPreferenceGroup;
-    @Mock
-    private CarAudioManager mCarAudioManager;
-    @Mock
-    private Ringtone mRingtone;
-
-    /** Extend class to provide test resource which doesn't require internal android resources. */
-    public static class TestVolumeSettingsPreferenceController extends
-            VolumeSettingsPreferenceController {
-
-        public TestVolumeSettingsPreferenceController(Context context, String preferenceKey,
-                FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
-            super(context, preferenceKey, fragmentController, uxRestrictions);
-        }
-
-        @Override
-        public int carVolumeItemsXml() {
-            return R.xml.test_car_volume_items;
-        }
-    }
-
-    @Before
-    public void setUp() throws CarNotConnectedException {
-        MockitoAnnotations.initMocks(this);
-        ShadowCar.setCarManager(Car.AUDIO_SERVICE, mCarAudioManager);
-        ShadowRingtoneManager.setRingtone(mRingtone);
-
-        Context context = RuntimeEnvironment.application;
-        mPreferenceGroup = new LogicalPreferenceGroup(context);
-        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(context,
-                TestVolumeSettingsPreferenceController.class, mPreferenceGroup);
-        mController = mPreferenceControllerHelper.getController();
-
-        when(mCarAudioManager.getVolumeGroupCount()).thenReturn(1);
-        when(mCarAudioManager.getUsagesForVolumeGroupId(GROUP_ID)).thenReturn(new int[]{1, 2});
-        when(mCarAudioManager.getGroupMinVolume(GROUP_ID)).thenReturn(TEST_MIN_VOLUME);
-        when(mCarAudioManager.getGroupVolume(GROUP_ID)).thenReturn(TEST_VOLUME);
-        when(mCarAudioManager.getGroupMaxVolume(GROUP_ID)).thenReturn(TEST_MAX_VOLUME);
-    }
-
-    @After
-    public void tearDown() {
-        ShadowCar.reset();
-        ShadowRingtoneManager.reset();
-    }
-
-    @Test
-    public void testRefreshUi_serviceNotStarted() {
-        mController.refreshUi();
-        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testRefreshUi_serviceStarted() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void onServiceConnected_registersVolumeCallback() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-
-        verify(mCarAudioManager).registerCarVolumeCallback(mController.mVolumeChangeCallback);
-    }
-
-    @Test
-    public void testRefreshUi_serviceStarted_multipleCalls() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-
-        // Calling this multiple times shouldn't increase the number of elements.
-        mController.refreshUi();
-        mController.refreshUi();
-        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void testRefreshUi_createdPreferenceHasMinMax() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
-        assertThat(preference.getMin()).isEqualTo(TEST_MIN_VOLUME);
-        assertThat(preference.getValue()).isEqualTo(TEST_VOLUME);
-        assertThat(preference.getMax()).isEqualTo(TEST_MAX_VOLUME);
-    }
-
-    @Test
-    public void testOnPreferenceChange_ringtonePlays() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
-        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
-        verify(mRingtone).play();
-    }
-
-    @Test
-    public void testOnPreferenceChange_audioManagerSet() throws CarNotConnectedException {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
-        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
-        verify(mCarAudioManager).setGroupVolume(GROUP_ID, TEST_NEW_VOLUME, 0);
-    }
-
-    @Test
-    public void onGroupVolumeChanged_sameValue_doesNotUpdateVolumeSeekbar() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        SeekBarPreference preference = spy((SeekBarPreference) mPreferenceGroup.getPreference(0));
-        mController.mVolumeChangeCallback.onGroupVolumeChanged(ZONE_ID, GROUP_ID, /* flags= */ 0);
-
-        verify(preference, never()).setValue(any(Integer.class));
-    }
-
-    @Test
-    public void onGroupVolumeChanged_differentValue_updatesVolumeSeekbar() {
-        mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
-        mController.refreshUi();
-        when(mCarAudioManager.getGroupVolume(GROUP_ID)).thenReturn(TEST_NEW_VOLUME);
-        mController.mVolumeChangeCallback.onGroupVolumeChanged(ZONE_ID, GROUP_ID, /* flags= */ 0);
-
-        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
-        assertThat(preference.getValue()).isEqualTo(TEST_NEW_VOLUME);
-    }
-}
diff --git a/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
deleted file mode 100644
index 52cc56a..0000000
--- a/tests/robotests/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * 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.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.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.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();
-    }
-}
diff --git a/tests/unit/res/values/strings.xml b/tests/unit/res/values/strings.xml
index 4ce9833..554e09b 100644
--- a/tests/unit/res/values/strings.xml
+++ b/tests/unit/res/values/strings.xml
@@ -25,4 +25,7 @@
 
     <string name="fancy_device_admin_label" translatable="false">LordOfTheSevenReceiverKingdoms</string>
     <string name="fancy_device_admin_description" translatable="false">One Receiver to Rule them All</string>
+
+    <string name="test_volume_call" translatable="false">Call Volume</string>
+    <string name="test_volume_music" translatable="false">Music Volume</string>
 </resources>
diff --git a/tests/unit/res/xml/test_car_volume_items.xml b/tests/unit/res/xml/test_car_volume_items.xml
new file mode 100644
index 0000000..3ac7903
--- /dev/null
+++ b/tests/unit/res/xml/test_car_volume_items.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<!--
+  We use a separate test file due to the difficulty accessing android internal resources in tests.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+    <item car:usage="voice_communication"
+          car:titleText="@string/test_volume_call"
+          car:icon="@drawable/test_icon"/>
+    <item car:usage="voice_communication_signalling"
+          car:titleText="@string/test_volume_call"
+          car:icon="@drawable/test_icon"/>
+    <item car:usage="media"
+          car:titleText="@string/test_volume_music"
+          car:icon="@drawable/test_icon"/>
+</carVolumeItems>
diff --git a/tests/unit/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java b/tests/unit/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..1d233a7
--- /dev/null
+++ b/tests/unit/src/com/android/car/settings/sound/VolumeSettingsPreferenceControllerTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2021 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.sound;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestUtil;
+import com.android.car.settings.common.SeekBarPreference;
+import com.android.car.settings.testutils.TestLifecycleOwner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VolumeSettingsPreferenceControllerTest {
+
+    private static final int ZONE_ID = 1;
+    private static final int GROUP_ID = 0;
+    private static final int TEST_MIN_VOLUME = 0;
+    private static final int TEST_VOLUME = 40;
+    private static final int TEST_NEW_VOLUME = 80;
+    private static final int TEST_MAX_VOLUME = 100;
+
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private LifecycleOwner mLifecycleOwner;
+    private PreferenceGroup mPreferenceGroup;
+    private VolumeSettingsPreferenceController mPreferenceController;
+    private CarUxRestrictions mCarUxRestrictions;
+
+    @Mock
+    private FragmentController mFragmentController;
+    @Mock
+    private Car mCar;
+    @Mock
+    private CarAudioManager mCarAudioManager;
+    @Mock
+    private VolumeSettingsRingtoneManager mRingtoneManager;
+
+    @Before
+    @UiThreadTest
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = new TestLifecycleOwner();
+
+        mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+        when(mCar.getCarManager(Car.AUDIO_SERVICE)).thenReturn(mCarAudioManager);
+        when(mCarAudioManager.getVolumeGroupCount()).thenReturn(1);
+        when(mCarAudioManager.getUsagesForVolumeGroupId(GROUP_ID)).thenReturn(new int[]{1, 2});
+        when(mCarAudioManager.getGroupMinVolume(GROUP_ID)).thenReturn(TEST_MIN_VOLUME);
+        when(mCarAudioManager.getGroupVolume(GROUP_ID)).thenReturn(TEST_VOLUME);
+        when(mCarAudioManager.getGroupMaxVolume(GROUP_ID)).thenReturn(TEST_MAX_VOLUME);
+
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext);
+        mPreferenceGroup = new LogicalPreferenceGroup(mContext);
+        screen.addPreference(mPreferenceGroup);
+        mPreferenceController = new TestVolumeSettingsPreferenceController(mContext,
+                "key", mFragmentController, mCarUxRestrictions, mCar, mRingtoneManager);
+        PreferenceControllerTestUtil.assignPreference(mPreferenceController, mPreferenceGroup);
+    }
+
+    @Test
+    public void testRefreshUi_serviceNotStarted() {
+        mPreferenceController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testRefreshUi_serviceStarted() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onServiceConnected_registersVolumeCallback() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+
+        verify(mCarAudioManager).registerCarVolumeCallback(
+                mPreferenceController.mVolumeChangeCallback);
+    }
+
+    @Test
+    public void testRefreshUi_serviceStarted_multipleCalls() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+
+        // Calling this multiple times shouldn't increase the number of elements.
+        mPreferenceController.refreshUi();
+        mPreferenceController.refreshUi();
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRefreshUi_createdPreferenceHasMinMax() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        assertThat(preference.getMin()).isEqualTo(TEST_MIN_VOLUME);
+        assertThat(preference.getValue()).isEqualTo(TEST_VOLUME);
+        assertThat(preference.getMax()).isEqualTo(TEST_MAX_VOLUME);
+    }
+
+    @Test
+    public void testOnPreferenceChange_ringtonePlays() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
+        verify(mRingtoneManager).playAudioFeedback(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testOnPreferenceChange_audioManagerSet() throws CarNotConnectedException {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        preference.getOnPreferenceChangeListener().onPreferenceChange(preference, TEST_NEW_VOLUME);
+        verify(mCarAudioManager).setGroupVolume(GROUP_ID, TEST_NEW_VOLUME, 0);
+    }
+
+    @Test
+    public void onGroupVolumeChanged_sameValue_doesNotUpdateVolumeSeekbar() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        SeekBarPreference preference = spy((SeekBarPreference) mPreferenceGroup.getPreference(0));
+        mPreferenceController.mVolumeChangeCallback.onGroupVolumeChanged(ZONE_ID,
+                GROUP_ID, /* flags= */ 0);
+
+        verify(preference, never()).setValue(any(Integer.class));
+    }
+
+    @Test
+    public void onGroupVolumeChanged_differentValue_updatesVolumeSeekbar() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.refreshUi();
+        when(mCarAudioManager.getGroupVolume(GROUP_ID)).thenReturn(TEST_NEW_VOLUME);
+        mPreferenceController.mVolumeChangeCallback.onGroupVolumeChanged(ZONE_ID,
+                GROUP_ID, /* flags= */ 0);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        SeekBarPreference preference = (SeekBarPreference) mPreferenceGroup.getPreference(0);
+        assertThat(preference.getValue()).isEqualTo(TEST_NEW_VOLUME);
+    }
+
+    private static class TestVolumeSettingsPreferenceController extends
+            VolumeSettingsPreferenceController {
+
+        TestVolumeSettingsPreferenceController(Context context, String preferenceKey,
+                FragmentController fragmentController,
+                CarUxRestrictions uxRestrictions, Car car,
+                VolumeSettingsRingtoneManager ringtoneManager) {
+            super(context, preferenceKey, fragmentController, uxRestrictions, car, ringtoneManager);
+        }
+
+        @Override
+        public int carVolumeItemsXml() {
+            return R.xml.test_car_volume_items;
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java b/tests/unit/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
new file mode 100644
index 0000000..669cce7
--- /dev/null
+++ b/tests/unit/src/com/android/car/settings/units/UnitsBasePreferenceControllerTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2021 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.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+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.LifecycleOwner;
+import androidx.preference.ListPreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.PreferenceControllerTestUtil;
+import com.android.car.settings.testutils.TestLifecycleOwner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.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 = ApplicationProvider.getApplicationContext();
+    private LifecycleOwner mLifecycleOwner;
+    private ListPreference mPreference;
+    private UnitsBasePreferenceController mPreferenceController;
+    private CarUxRestrictions mCarUxRestrictions;
+
+    @Mock
+    private FragmentController mFragmentController;
+    @Mock
+    private CarPropertyManager mCarPropertyManager;
+    @Mock
+    private CarPropertyValue mCarPropertyValue;
+    @Mock
+    private CarUnitsManager mCarUnitsManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = new TestLifecycleOwner();
+
+        doNothing().when(mCarUnitsManager).registerCarServiceListener(any());
+        when(mCarUnitsManager.getUnitsSupportedByProperty(TEST_PROPERTY_ID))
+                .thenReturn(AVAILABLE_UNITS);
+        updateDefaultUnit(DEFAULT_UNIT);
+
+        mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+        mPreference = new ListPreference(mContext);
+        mPreferenceController = new TestUnitsBasePreferenceController(mContext,
+                /* preferenceKey= */ "key", mFragmentController, mCarUxRestrictions);
+        PreferenceControllerTestUtil.assignPreference(mPreferenceController, mPreference);
+        mPreferenceController.onCreate(mLifecycleOwner);
+    }
+
+    @Test
+    public void onCreate_registersCarServiceListener() {
+        verify(mCarUnitsManager)
+                .registerCarServiceListener(mPreferenceController.mOnCarServiceListener);
+    }
+
+    @Test
+    public void onCreate_preferenceIsConditionallyUnavailable() {
+        assertThat(mPreferenceController.getAvailabilityStatus())
+                .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_availableUnitsExist_preferenceIsAvailable() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+        assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(
+                PreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_noAvailableUnits_preferenceIsConditionallyUnavailable() {
+        when(mCarUnitsManager.getUnitsSupportedByProperty(TEST_PROPERTY_ID))
+                .thenReturn(null);
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+
+        assertThat(mPreferenceController.getAvailabilityStatus())
+                .isEqualTo(PreferenceController.CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsEntriesOfSupportedUnits() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        CharSequence[] expectedEntries = mPreferenceController.getEntriesOfSupportedUnits();
+
+        assertThat(mPreference.getEntries()).isEqualTo(expectedEntries);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsSupportedUnitsIdsAsEntryValues() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        CharSequence[] expectedEntryValues = mPreferenceController.getIdsOfSupportedUnits();
+
+        assertThat(mPreference.getEntryValues()).isEqualTo(expectedEntryValues);
+    }
+
+    @Test
+    public void onCarServiceConnected_setsUnitBeingUsedAsPreferenceValue() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        String expectedValue = Integer.toString(DEFAULT_UNIT.getId());
+
+        assertThat(mPreference.getValue()).isEqualTo(expectedValue);
+    }
+
+    @Test
+    public void onPreferenceChanged_runsSetUnitUsedByPropertyWithNewUnit() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        mPreferenceController.handlePreferenceChanged(mPreference,
+                Integer.toString(UnitsMap.SECS.getId()));
+
+        assertThat(mCarUnitsManager.getUnitUsedByProperty(TEST_PROPERTY_ID))
+                .isEqualTo(UnitsMap.SECS);
+    }
+
+    @Test
+    public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitIdAsValue() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        Unit newUnit = UnitsMap.SECS;
+        when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+        when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+        mPreferenceController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+        assertThat(mPreference.getValue()).isEqualTo(Integer.toString(newUnit.getId()));
+    }
+
+    @Test
+    public void onPropertyChanged_propertyStatusIsAvailable_setsNewUnitAbbreviationAsSummary() {
+        mPreferenceController.mOnCarServiceListener.handleServiceConnected(mCarPropertyManager);
+        Unit newUnit = UnitsMap.SECS;
+        when(mCarPropertyValue.getStatus()).thenReturn(CarPropertyValue.STATUS_AVAILABLE);
+        when(mCarPropertyValue.getValue()).thenReturn(newUnit.getId());
+        mPreferenceController.mCarPropertyEventCallback.onChangeEvent(mCarPropertyValue);
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(mContext.getString(newUnit.getAbbreviationResId()));
+    }
+
+    @Test
+    public void onDestroy_disconnectsCarUnitsManager() {
+        mPreferenceController.onDestroy(mLifecycleOwner);
+
+        verify(mCarUnitsManager).disconnect();
+    }
+
+    @Test
+    public void onDestroy_unregistersCarServiceListener() {
+        mPreferenceController.onDestroy(mLifecycleOwner);
+
+        verify(mCarUnitsManager).unregisterCarServiceListener();
+    }
+
+    private void updateDefaultUnit(Unit unit) {
+        when(mCarUnitsManager.getUnitUsedByProperty(TEST_PROPERTY_ID))
+                .thenReturn(unit);
+    }
+
+    private 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;
+        }
+
+        @Override
+        public boolean handlePreferenceChanged(ListPreference preference, Object newValue) {
+            int unitId = Integer.parseInt((String) newValue);
+            Unit newUnit = UnitsMap.MAP.get(unitId);
+            if (newUnit != null) {
+                updateDefaultUnit(newUnit);
+            }
+            return super.handlePreferenceChanged(preference, newValue);
+        }
+
+        @Override
+        CarUnitsManager createCarUnitsManager() {
+            return mCarUnitsManager;
+        }
+    }
+}