Merge changes I94e08d61,I431fe6b7 into sc-dev

* changes:
  Read the on-device XML configurations on initialization.
  Convert per state thresholds read from XML configs.
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index cb52c42..3e6c3c5 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -138,7 +138,8 @@
     /**
      * Speed of the vehicle in meters per second.
      *
-     * <p>PERF_VEHICLE_SPEED property is {@link VehiclePropertyAccess#READ} access, {@link
+     * <p>PERF_VEHICLE_SPEED property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
      * CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}, and returns a Float type value.
      *
      * <p>When the vehicle is moving forward, PERF_VEHICLE_SPEED is positive and negative when the
@@ -207,9 +208,9 @@
     /**
      * Reports wheel ticks.
      *
-     * <p>WHEEL_TICK property is {@link VehiclePropertyAccess#Read} access,
-     * {@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS} and returns a Long[] type
-     * value.
+     * <p>WHEEL_TICK property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
+     * CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS} and returns a Long[] type value.
      *
      * <p>The first element in the array is a reset count.  A reset indicates
      * previous tick counts are not comparable with this and future ones.  Some
diff --git a/car-lib/src/android/car/evs/CarEvsManager.java b/car-lib/src/android/car/evs/CarEvsManager.java
index c1eb21b..65ac9e5 100644
--- a/car-lib/src/android/car/evs/CarEvsManager.java
+++ b/car-lib/src/android/car/evs/CarEvsManager.java
@@ -23,23 +23,22 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.car.annotation.RequiredFeature;
 import android.car.Car;
 import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Slog;
-import android.view.SurfaceHolder;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.util.concurrent.Executor;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Provides an application interface for interativing with the Extended View System service.
diff --git a/car-lib/src/android/car/hardware/CarSensorManager.java b/car-lib/src/android/car/hardware/CarSensorManager.java
index b34e171..f933d9e 100644
--- a/car-lib/src/android/car/hardware/CarSensorManager.java
+++ b/car-lib/src/android/car/hardware/CarSensorManager.java
@@ -51,7 +51,12 @@
     public static final int SENSOR_TYPE_RESERVED1                   = 1;
     /**
      * This sensor represents vehicle speed in m/s.
-     * Sensor data in {@link CarSensorEvent} is a float which will be >= 0.
+     *
+     * <p>Sensor data in {@link CarSensorEvent} is a float. When the vehicle is moving forward,
+     * SENSOR_TYPE_CAR_SPEED is positive and negative when the vehicle is moving backward. Also,
+     * this value is independent of SENSOR_TYPE_GEAR. For example, if SENSOR_TYPE_GEAR is {@link
+     * CarSensorEvent#GEAR_NEUTRAL}, SENSOR_TYPE_CAR_SPEED is positive when the vehicle is moving
+     * forward, negative when moving backward, and zero when not moving.
      */
     public static final int SENSOR_TYPE_CAR_SPEED                   = 0x11600207;
     /**
diff --git a/car-lib/src/android/car/input/CarInputHandlingService.java b/car-lib/src/android/car/input/CarInputHandlingService.java
index 02a4929..c7e3f96 100644
--- a/car-lib/src/android/car/input/CarInputHandlingService.java
+++ b/car-lib/src/android/car/input/CarInputHandlingService.java
@@ -15,6 +15,8 @@
  */
 package android.car.input;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
+
 import android.annotation.CallSuper;
 import android.annotation.MainThread;
 import android.annotation.SystemApi;
@@ -32,6 +34,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -57,6 +61,7 @@
  */
 @SystemApi
 @Deprecated
+@ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
 public abstract class CarInputHandlingService extends Service {
     private static final String TAG = CarLibLog.TAG_INPUT;
     private static final boolean DBG = false;
diff --git a/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java b/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java
new file mode 100644
index 0000000..dfbe530
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to mark code to be excluded from coverage report.
+ */
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface ExcludeFromCodeCoverageGeneratedReport {
+
+    // Reason annotation and its associated constant values
+    int DEPRECATED_CODE = 0;
+    int BOILERPLATE_CODE = 1;
+    int DUMP_INFO = 2;
+    int DEBUGGING_CODE = 3;
+
+    @IntDef(prefix = "REASON_", value = {
+            DEPRECATED_CODE,
+            BOILERPLATE_CODE,
+            DUMP_INFO,
+            DEBUGGING_CODE
+    })
+    @interface Reason { }
+
+    /**
+     * The reason explaining why the code is being excluded from the code coverage report.
+     * <p>
+     * Possible reasons to exclude code from coverage report are:
+     * <p><ul>
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEPRECATED_CODE} to exclude deprecated
+     * code from coverage report
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#BOILERPLATE_CODE} to exclude boilerplate
+     * code like {@link java.lang.Object} methods, {@link android.os.Parcel} methods, etc
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DUMP_INFO} to exclude dump info methods
+     * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEBUGGING_CODE} to exclude debugging
+     * purpose
+     * code
+     * </ul><p>
+     */
+    @Reason int reason();
+
+    /**
+     * Optional field used to provide extra details about the excluded code (e.g. it can be used to
+     * tag a follow up bug).
+     */
+    String details() default "";
+}
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 3107597..df09aae 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -40,8 +40,6 @@
 PRODUCT_PACKAGES += \
     DefaultStorageMonitoringCompanionApp \
     EmbeddedKitchenSinkApp \
-    DirectRenderingCluster \
-    ClusterHomeSample \
     GarageModeTestApp \
     ExperimentalCarService \
     BugReportApp \
@@ -52,6 +50,15 @@
 BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
 endif
 
+# ClusterOsDouble is the testing app to test Cluster2 framework and it can handle Cluster VHAL
+# and do some Cluster OS role.
+ifeq ($(ENABLE_CLUSTER_OS_DOUBLE), true)
+PRODUCT_PACKAGES += ClusterHomeSample ClusterOsDouble
+else
+# DirectRenderingCluster is the sample app for the old Cluster framework.
+PRODUCT_PACKAGES += DirectRenderingCluster
+endif  # ENABLE_CLUSTER_OS_DOUBLE
+
 PRODUCT_COPY_FILES += \
     frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf
 
diff --git a/cpp/computepipe/aidl/Android.bp b/cpp/computepipe/aidl/Android.bp
index 2cf9ca1..bb0e1e3 100644
--- a/cpp/computepipe/aidl/Android.bp
+++ b/cpp/computepipe/aidl/Android.bp
@@ -13,6 +13,9 @@
         "android.hardware.graphics.common-V2",
     ],
     stability: "vintf",
+    // Automotive has different platform schedule. We shouldn't need to
+    // freeze Auto interfaces when the mainline Android launches.
+    owner: "automotive",
     backend: {
         java: {
             enabled: false,
@@ -33,6 +36,9 @@
         "android/automotive/computepipe/*.aidl",
     ],
     stability: "vintf",
+    // Automotive has different platform schedule. We shouldn't need to
+    // freeze Auto interfaces when the mainline Android launches.
+    owner: "automotive",
     backend: {
         java: {
             enabled: false,
diff --git a/cpp/powerpolicy/server/src/PolicyManager.cpp b/cpp/powerpolicy/server/src/PolicyManager.cpp
index 55100e9..2681f51 100644
--- a/cpp/powerpolicy/server/src/PolicyManager.cpp
+++ b/cpp/powerpolicy/server/src/PolicyManager.cpp
@@ -48,7 +48,7 @@
 namespace {
 
 // Vendor power policy filename.
-constexpr const char* kVendorPolicyFile = "/vendor/etc/power_policy.xml";
+constexpr const char* kVendorPolicyFile = "/vendor/etc/automotive/power_policy.xml";
 
 // Tags and attributes in vendor power policy XML file.
 constexpr const char* kTagRoot = "powerPolicy";
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index aeb384a..5870b32 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -19,6 +19,8 @@
 import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
 
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -56,6 +58,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.car.hal.InputHalService;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.common.UserHelperLite;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
@@ -689,6 +692,7 @@
     }
 
     @Override
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter writer) {
         writer.println("*Input Service*");
         writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms");
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 7a8c9cb..dcc9371 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -154,6 +154,8 @@
             "get-user-auth-association";
     private static final String COMMAND_SET_USER_AUTH_ASSOCIATION =
             "set-user-auth-association";
+    private static final String COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE =
+            "set-start-bg-users-on-garage-mode";
     private static final String COMMAND_DEFINE_POWER_POLICY = "define-power-policy";
     private static final String COMMAND_APPLY_POWER_POLICY = "apply-power-policy";
     private static final String COMMAND_DEFINE_POWER_POLICY_GROUP = "define-power-policy-group";
@@ -190,7 +192,7 @@
     // This map is looked up first, then USER_BUILD_COMMAND_TO_PERMISSION_MAP
     private static final ArrayMap<String, String[]> USER_BUILD_COMMAND_TO_PERMISSIONS_MAP;
     static {
-        USER_BUILD_COMMAND_TO_PERMISSIONS_MAP = new ArrayMap<>(6);
+        USER_BUILD_COMMAND_TO_PERMISSIONS_MAP = new ArrayMap<>(7);
         USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_GET_INITIAL_USER_INFO,
                 CREATE_OR_MANAGE_USERS_PERMISSIONS);
         USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SWITCH_USER,
@@ -203,6 +205,8 @@
                 CREATE_OR_MANAGE_USERS_PERMISSIONS);
         USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SET_USER_AUTH_ASSOCIATION,
                 CREATE_OR_MANAGE_USERS_PERMISSIONS);
+        USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE,
+                CREATE_OR_MANAGE_USERS_PERMISSIONS);
     }
 
     // List of commands allowed in user build. All these command should be protected with
@@ -526,6 +530,11 @@
         pw.printf("\t  %s\n", VALID_USER_AUTH_TYPES_HELP);
         pw.printf("\t  %s\n", VALID_USER_AUTH_SET_VALUES_HELP);
 
+        pw.printf("\t%s [true|false]\n", COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE);
+        pw.println("\t  Controls backgroud user start and stop during garage mode.");
+        pw.println("\t  If false, garage mode operations (background users start at garage mode"
+                + " entry and background users stop at garage mode exit) will be skipped.");
+
         pw.printf("\t  %s [%s|%s|%s|%s]\n", COMMAND_SILENT_MODE, SILENT_MODE_FORCED_SILENT,
                 SILENT_MODE_FORCED_NON_SILENT, SILENT_MODE_NON_FORCED, PARAM_QUERY_MODE);
         pw.println("\t  Forces silent mode silent or non-silent. With query (or no command) "
@@ -856,6 +865,9 @@
             case COMMAND_SET_USER_AUTH_ASSOCIATION:
                 setUserAuthAssociation(args, writer);
                 break;
+            case COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE:
+                setStartBackgroundUsersOnGarageMode(args, writer);
+                break;
             case COMMAND_EMULATE_DRIVING_STATE:
                 emulateDrivingState(args, writer);
                 break;
@@ -879,6 +891,18 @@
         return RESULT_OK;
     }
 
+    private void setStartBackgroundUsersOnGarageMode(String[] args, IndentingPrintWriter writer) {
+        if (args.length < 2) {
+            writer.println("Insufficient number of args");
+            return;
+        }
+
+        boolean enabled = Boolean.parseBoolean(args[1]);
+        Slog.d(TAG, "setStartBackgroundUsersOnGarageMode(): " + (enabled ? "enabled" : "disabled"));
+        mCarUserService.setStartBackgroundUsersOnGarageMode(enabled);
+        writer.printf("StartBackgroundUsersOnGarageMode set to %b\n", enabled);
+    }
+
     private void startFixedActivity(String[] args, IndentingPrintWriter writer) {
         if (args.length != 4) {
             writer.println("Incorrect number of arguments");
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 091b505..62af82d 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -39,6 +39,7 @@
  */
 /* package */ class CarAudioDeviceInfo {
 
+    public static final int DEFAULT_SAMPLE_RATE = 48000;
     private final AudioDeviceInfo mAudioDeviceInfo;
     private final int mSampleRate;
     private final int mEncodingFormat;
@@ -58,10 +59,10 @@
     CarAudioDeviceInfo(AudioDeviceInfo audioDeviceInfo) {
         mAudioDeviceInfo = audioDeviceInfo;
         mSampleRate = getMaxSampleRate(audioDeviceInfo);
-        mEncodingFormat = getEncodingFormat(audioDeviceInfo);
+        mEncodingFormat = AudioFormat.ENCODING_PCM_16BIT;
         mChannelCount = getMaxChannels(audioDeviceInfo);
-        final AudioGain audioGain = Objects.requireNonNull(
-                getAudioGain(), "No audio gain on device port " + audioDeviceInfo);
+        AudioGain audioGain = Objects.requireNonNull(getAudioGain(audioDeviceInfo.getPort()),
+                "No audio gain on device port " + audioDeviceInfo);
         mDefaultGain = audioGain.defaultValue();
         mMaxGain = audioGain.maxValue();
         mMinGain = audioGain.minValue();
@@ -110,6 +111,15 @@
         return mStepValue;
     }
 
+    /**
+     * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}.
+     * This is useful for inspecting the configuration data associated with this gain controller
+     * (min/max/step/default).
+     */
+    AudioGain getAudioGain() {
+        return getAudioGain(getAudioDevicePort());
+    }
+
     // Input is in millibels
     void setCurrentGain(int gainInMillibels) {
         // Clamp the incoming value to our valid range.  Out of range values ARE legal input
@@ -148,10 +158,10 @@
         }
     }
 
-    private int getMaxSampleRate(AudioDeviceInfo info) {
+    private static int getMaxSampleRate(AudioDeviceInfo info) {
         int[] sampleRates = info.getSampleRates();
         if (sampleRates == null || sampleRates.length == 0) {
-            return 48000;
+            return DEFAULT_SAMPLE_RATE;
         }
         int sampleRate = sampleRates[0];
         for (int i = 1; i < sampleRates.length; i++) {
@@ -162,19 +172,7 @@
         return sampleRate;
     }
 
-    /** Always returns {@link AudioFormat#ENCODING_PCM_16BIT} as for now */
-    private int getEncodingFormat(AudioDeviceInfo info) {
-        return AudioFormat.ENCODING_PCM_16BIT;
-    }
-
-    /**
-     * Gets the maximum channel count for a given {@link AudioDeviceInfo}
-     *
-     * @param info {@link AudioDeviceInfo} instance to get maximum channel count for
-     * @return Maximum channel count for a given {@link AudioDeviceInfo},
-     * 1 (mono) if there is no channel masks configured
-     */
-    private int getMaxChannels(AudioDeviceInfo info) {
+    private static int getMaxChannels(AudioDeviceInfo info) {
         int numChannels = 1;
         int[] channelMasks = info.getChannelMasks();
         if (channelMasks == null) {
@@ -189,13 +187,7 @@
         return numChannels;
     }
 
-    /**
-     * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}.
-     * This is useful for inspecting the configuration data associated with this gain controller
-     * (min/max/step/default).
-     */
-    AudioGain getAudioGain() {
-        final AudioDevicePort audioPort = getAudioDevicePort();
+    private static AudioGain getAudioGain(AudioDevicePort audioPort) {
         if (audioPort != null && audioPort.gains().length > 0) {
             for (AudioGain audioGain : audioPort.gains()) {
                 if ((audioGain.mode() & AudioGain.MODE_JOINT) != 0) {
@@ -209,7 +201,7 @@
     /**
      * Constraints applied to gain configuration, see also audio_policy_configuration.xml
      */
-    private AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
+    private static AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
         Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue());
         Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue())
                 && (audioGain.defaultValue() <= audioGain.maxValue()));
diff --git a/service/src/com/android/car/audio/CarDucking.java b/service/src/com/android/car/audio/CarDucking.java
index 3e05b95..0ac53c4 100644
--- a/service/src/com/android/car/audio/CarDucking.java
+++ b/service/src/com/android/car/audio/CarDucking.java
@@ -58,25 +58,43 @@
         }
     }
 
-    public void onFocusChange(int audioZoneId, @NonNull List<AudioFocusInfo> focusHolders) {
+    @Override
+    public void onFocusChange(int[] audioZoneIds,
+            @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId) {
         synchronized (mLock) {
-            CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(audioZoneId);
-            CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
-                    focusHolders);
-            mCurrentDuckingInfo.put(audioZoneId, newDuckingInfo);
-            mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfo);
+            List<CarDuckingInfo> newDuckingInfos = new ArrayList<>(audioZoneIds.length);
+            for (int i = 0; i < audioZoneIds.length; i++) {
+                int zoneId = audioZoneIds[i];
+                List<AudioFocusInfo> focusHolders = focusHoldersByZoneId.get(zoneId);
+                CarDuckingInfo newDuckingInfo = updateDuckingForZoneIdLocked(zoneId, focusHolders);
+                newDuckingInfos.add(newDuckingInfo);
+            }
+            mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfos);
         }
     }
 
+    @GuardedBy("mLock")
+    private CarDuckingInfo updateDuckingForZoneIdLocked(int zoneId,
+            List<AudioFocusInfo> focusHolders) {
+        CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(zoneId);
+        CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
+                focusHolders);
+        mCurrentDuckingInfo.put(zoneId, newDuckingInfo);
+        return newDuckingInfo;
+    }
+
     public void dump(IndentingPrintWriter writer) {
         writer.printf("*%s*\n", TAG);
         writer.increaseIndent();
-        for (int i = 0; i < mCurrentDuckingInfo.size(); i++) {
-            mCurrentDuckingInfo.valueAt(i).dump(writer);
+        synchronized (mLock) {
+            for (int i = 0; i < mCurrentDuckingInfo.size(); i++) {
+                mCurrentDuckingInfo.valueAt(i).dump(writer);
+            }
         }
         writer.decreaseIndent();
     }
 
+    @GuardedBy("mLock")
     private CarDuckingInfo generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo,
             List<AudioFocusInfo> focusHolders) {
         int zoneId = oldDuckingInfo.mZoneId;
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index 4f19e1a..ca5d532 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -148,16 +148,19 @@
     }
 
     void setRestrictFocus(boolean isFocusRestricted) {
+        int[] zoneIds = new int[mFocusZones.size()];
         for (int i = 0; i < mFocusZones.size(); i++) {
+            zoneIds[i] = mFocusZones.keyAt(i);
             mFocusZones.valueAt(i).setRestrictFocus(isFocusRestricted);
         }
+        notifyFocusCallback(zoneIds);
     }
 
     @Override
     public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
         int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
         getCarAudioFocusForZoneId(zoneId).onAudioFocusRequest(afi, requestResult);
-        notifyFocusCallback(zoneId);
+        notifyFocusCallback(new int[]{zoneId});
     }
 
     /**
@@ -169,7 +172,7 @@
     public void onAudioFocusAbandon(AudioFocusInfo afi) {
         int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
         getCarAudioFocusForZoneId(zoneId).onAudioFocusAbandon(afi);
-        notifyFocusCallback(zoneId);
+        notifyFocusCallback(new int[]{zoneId});
     }
 
     @NonNull
@@ -203,13 +206,18 @@
         return zoneId;
     }
 
-    private void notifyFocusCallback(int audioZoneId) {
+    private void notifyFocusCallback(int[] zoneIds) {
         if (mCarFocusCallback == null) {
             return;
         }
+        SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId = new SparseArray<>();
+        for (int i = 0; i < zoneIds.length; i++) {
+            int zoneId = zoneIds[i];
+            List<AudioFocusInfo> focusHolders = mFocusZones.get(zoneId).getAudioFocusHolders();
+            focusHoldersByZoneId.put(zoneId, focusHolders);
+        }
 
-        List<AudioFocusInfo> focusHolders = mFocusZones.get(audioZoneId).getAudioFocusHolders();
-        mCarFocusCallback.onFocusChange(audioZoneId, focusHolders);
+        mCarFocusCallback.onFocusChange(zoneIds, focusHoldersByZoneId);
     }
 
     void dump(IndentingPrintWriter writer) {
@@ -242,9 +250,11 @@
         /**
          * Called after a focus request or abandon call is handled.
          *
-         * @param audioZoneId ID of the zone where the change took place
-         * @param focusHolders list of {@link AudioFocusInfo}s holding focus in specified audio zone
+         * @param audioZoneIds IDs of the zones where the changes took place
+         * @param focusHoldersByZoneId sparse array by zone ID, where each value is a list of
+         * {@link AudioFocusInfo}s holding focus in specified audio zone
          */
-        void onFocusChange(int audioZoneId, @NonNull List<AudioFocusInfo> focusHolders);
+        void onFocusChange(int[] audioZoneIds,
+                @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId);
     }
 }
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapper.java b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
index d42aa04..1866f33 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapper.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
@@ -103,9 +103,10 @@
      * Notifies HAL of changes in usages holding focus and the corresponding ducking changes for a
      * given zone.
      *
-     * @param carDuckingInfo information about focus and addresses to duck to relay to the HAL.
+     * @param carDuckingInfos list of information about focus and addresses to duck for each
+     * impacted zone to relay to the HAL.
      */
-    void onDevicesToDuckChange(@NonNull CarDuckingInfo carDuckingInfo);
+    void onDevicesToDuckChange(@NonNull List<CarDuckingInfo> carDuckingInfos);
 
     /**
      * Notifies HAL of changes in muting changes for all audio zones.
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
index b985407..4953f97 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
@@ -142,15 +142,18 @@
     }
 
     @Override
-    public void onDevicesToDuckChange(@NonNull CarDuckingInfo carDuckingInfo) {
-        Objects.requireNonNull(carDuckingInfo);
-        DuckingInfo duckingInfo = carDuckingInfo.generateDuckingInfo();
+    public void onDevicesToDuckChange(@NonNull List<CarDuckingInfo> carDuckingInfos) {
+        Objects.requireNonNull(carDuckingInfos);
+        DuckingInfo[] duckingInfos = new DuckingInfo[carDuckingInfos.size()];
+        for (int i = 0; i < carDuckingInfos.size(); i++) {
+            CarDuckingInfo info = Objects.requireNonNull(carDuckingInfos.get(i));
+            duckingInfos[i] = info.generateDuckingInfo();
+        }
 
         try {
-            mAudioControl.onDevicesToDuckChange(new DuckingInfo[] {duckingInfo});
+            mAudioControl.onDevicesToDuckChange(duckingInfos);
         } catch (RemoteException e) {
-            Slog.e(TAG, "onDevicesToDuckChange for zone " + carDuckingInfo.mZoneId
-                    + " failed", e);
+            Slog.e(TAG, "onDevicesToDuckChange failed", e);
         }
     }
 
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
index 9a0fa28..fd959cf 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
@@ -108,7 +108,7 @@
     }
 
     @Override
-    public void onDevicesToDuckChange(CarDuckingInfo carDuckingInfo) {
+    public void onDevicesToDuckChange(List<CarDuckingInfo> carDuckingInfos) {
         throw new UnsupportedOperationException("HAL ducking is unsupported for IAudioControl@1.0");
     }
 
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
index d99a22f..5354f23 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public void onDevicesToDuckChange(CarDuckingInfo carDuckingInfo) {
+    public void onDevicesToDuckChange(List<CarDuckingInfo> carDuckingInfos) {
         throw new UnsupportedOperationException("HAL ducking is unsupported for IAudioControl@2.0");
     }
 
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 3809ab0..f315dca 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -134,8 +134,8 @@
     private final Runnable mStartBackgroundUsers = new Runnable() {
         @Override
         public void run() {
-            ArrayList<Integer> startedUsers =
-                    CarLocalServices.getService(CarUserService.class).startAllBackgroundUsers();
+            ArrayList<Integer> startedUsers = CarLocalServices.getService(CarUserService.class)
+                    .startAllBackgroundUsersInGarageMode();
             Slogf.i(TAG, "Started background user during garage mode: %s", startedUsers);
             synchronized (mLock) {
                 // Stop stopping background users if there is any users left from last Garage mode,
@@ -157,8 +157,8 @@
             if (numberOfIdleJobsRunning() == 0) { // all jobs done or stopped.
                 // Keep user until job scheduling is stopped. Otherwise, it can crash jobs.
                 if (userToStop != UserHandle.USER_SYSTEM) {
-                    CarLocalServices.getService(CarUserService.class).stopBackgroundUser(
-                            userToStop);
+                    CarLocalServices.getService(CarUserService.class)
+                            .stopBackgroundUserInGagageMode(userToStop);
                     Slogf.i(TAG, "Stopping background user:%d remaining users:%d", userToStop,
                             mStartedBackgroundUsers.size() - 1);
                 }
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
index 9daabc0..afd8648 100644
--- a/service/src/com/android/car/power/PolicyReader.java
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -81,7 +81,7 @@
     static final int INVALID_POWER_STATE = -1;
 
     private static final String TAG = CarLog.tagFor(PolicyReader.class);
-    private static final String VENDOR_POLICY_PATH = "/vendor/etc/car/power_policy.xml";
+    private static final String VENDOR_POLICY_PATH = "/vendor/etc/automotive/power_policy.xml";
 
     private static final String NAMESPACE = null;
     private static final Set<String> VALID_VERSIONS = new ArraySet<>(Arrays.asList("1.0"));
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 0e8e9f2..de5739a 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -236,6 +236,15 @@
     private boolean mUxRestricted;
 
     /**
+     * If {@code false}, garage mode operations (background users start at garage mode entry and
+     * background users stop at garage mode exit) will be skipped. Controlled using car shell
+     * command {@code adb shell set-start-bg-users-on-garage-mode [true|false]}
+     * Purpose: Garage mode testing and simulation
+     */
+    @GuardedBy("mLockUser")
+    private boolean mStartBackgroundUsersOnGarageMode = true;
+
+    /**
      * Callback to notify {@code CarServiceHelper} about driving safety changes (through
      * {@link ICarServiceHelper#setSafetyMode(boolean).
      *
@@ -357,6 +366,8 @@
                 writer.println("FailedToCreateUserIds: " + mFailedToCreateUserIds);
             }
             writer.printf("Is UX restricted: %b\n", mUxRestricted);
+            writer.printf("Start Background Users On Garage Mode=%s\n",
+                    mStartBackgroundUsersOnGarageMode);
         }
 
         writer.println("SwitchGuestUserBeforeSleep: " + mSwitchGuestUserBeforeSleep);
@@ -1904,7 +1915,15 @@
      * @return list of background users started successfully.
      */
     @NonNull
-    public ArrayList<Integer> startAllBackgroundUsers() {
+    public ArrayList<Integer> startAllBackgroundUsersInGarageMode() {
+        synchronized (mLockUser) {
+            if (!mStartBackgroundUsersOnGarageMode) {
+                Slogf.i(TAG, "Background users are not started as mStartBackgroundUsersOnGarageMode"
+                        + " is false.");
+                return new ArrayList<>();
+            }
+        }
+
         ArrayList<Integer> users;
         synchronized (mLockUser) {
             users = new ArrayList<>(mBackgroundUsersToRestart);
@@ -2000,11 +2019,28 @@
     }
 
     /**
+     * Sets boolean to control background user operations during garage mode.
+     */
+    public void setStartBackgroundUsersOnGarageMode(boolean enable) {
+        synchronized (mLockUser) {
+            mStartBackgroundUsersOnGarageMode = enable;
+        }
+    }
+
+    /**
      * Stops a background user.
      *
      * @return whether stopping succeeds.
      */
-    public boolean stopBackgroundUser(@UserIdInt int userId) {
+    public boolean stopBackgroundUserInGagageMode(@UserIdInt int userId) {
+        synchronized (mLockUser) {
+            if (!mStartBackgroundUsersOnGarageMode) {
+                Slogf.i(TAG, "Background users are not stopped as mStartBackgroundUsersOnGarageMode"
+                        + " is false.");
+                return false;
+            }
+        }
+
         @UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
         if (UserStopResult.isSuccess(userStopStatus)) {
             // Remove the stopped user from the mBackgroundUserRestartedHere list.
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java
new file mode 100644
index 0000000..3cbfe86
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java
@@ -0,0 +1,51 @@
+/*
+ * 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.google.android.car.kitchensink.bluetooth;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.fragment.app.Fragment;
+
+public final class BluetoothConnectionPermissionChecker {
+    private BluetoothConnectionPermissionChecker() {
+    }
+
+    static boolean isPermissionGranted(Activity mActivity) {
+        return mActivity.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    static void requestPermission(Fragment fragment, @Nullable Runnable isGrantedRunnable,
+            @Nullable Runnable isNotGrantedRunnable) {
+        fragment.registerForActivityResult(new ActivityResultContracts.RequestPermission(),
+                isGranted -> {
+                    if (isGranted) {
+                        if (isGrantedRunnable != null) {
+                            isGrantedRunnable.run();
+                        }
+                    } else {
+                        if (isNotGrantedRunnable != null) {
+                            isNotGrantedRunnable.run();
+                        }
+                    }
+                }).launch(Manifest.permission.BLUETOOTH_CONNECT);
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
index 619a2b3..45da3a2 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
@@ -39,6 +39,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 
+import com.google.android.car.kitchensink.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
 
 public class BluetoothHeadsetFragment extends Fragment {
@@ -86,13 +87,19 @@
         mEndOutgoingCall = (Button) v.findViewById(R.id.bluetooth_end_outgoing_call);
         mOutgoingPhoneNumber = (EditText) v.findViewById(R.id.bluetooth_outgoing_phone_number);
 
-        // Pick a bluetooth device
-        mDevicePicker.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                launchDevicePicker();
-            }
-        });
+        if (!BluetoothConnectionPermissionChecker.isPermissionGranted(
+                (KitchenSinkActivity) getHost())) {
+            BluetoothConnectionPermissionChecker.requestPermission(this,
+                    this::setDevicePickerButtonClickable,
+                    () -> {
+                        setDevicePickerButtonUnclickable();
+                        Toast.makeText(getContext(),
+                                "Device picker can't run without BLUETOOTH_CONNECT permission. "
+                                        + "(You can change permissions in Settings.)",
+                                Toast.LENGTH_SHORT).show();
+                    }
+            );
+        }
 
         // Connect profile
         mConnect.setOnClickListener(new View.OnClickListener() {
@@ -177,6 +184,22 @@
         return v;
     }
 
+    private void setDevicePickerButtonClickable() {
+        mDevicePicker.setClickable(true);
+
+        // Pick a bluetooth device
+        mDevicePicker.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                launchDevicePicker();
+            }
+        });
+    }
+
+    private void setDevicePickerButtonUnclickable() {
+        mDevicePicker.setClickable(false);
+    }
+
     void launchDevicePicker() {
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
@@ -370,6 +393,13 @@
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mBluetoothAdapter.getProfileProxy(
             getContext(), new ProfileServiceListener(), BluetoothProfile.HEADSET_CLIENT);
+
+        if (BluetoothConnectionPermissionChecker.isPermissionGranted(
+                (KitchenSinkActivity) getHost())) {
+            setDevicePickerButtonClickable();
+        } else {
+            setDevicePickerButtonUnclickable();
+        }
     }
 
     @Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index e998fd8..7bf8e7f 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -128,6 +128,18 @@
             @Nullable Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.sms_received, container, false);
         mActivity = (KitchenSinkActivity) getHost();
+
+        if (!BluetoothConnectionPermissionChecker.isPermissionGranted(mActivity)) {
+            BluetoothConnectionPermissionChecker.requestPermission(this,
+                    this::registerMapServiceListenerAndNotificationReceiver,
+                    () -> {
+                    Toast.makeText(getContext(),
+                        "Connected devices can't be detected without BLUETOOTH_CONNECT "
+                                + "permission. (You can change permissions in Settings.)",
+                        Toast.LENGTH_SHORT).show();
+                });
+        }
+
         Button reply = (Button) v.findViewById(R.id.reply);
         Button checkMessages = (Button) v.findViewById(R.id.check_messages);
         mBluetoothDevice = (TextView) v.findViewById(R.id.bluetoothDevice);
@@ -208,7 +220,6 @@
             }
         });
 
-        mTransmissionStatusReceiver = new NotificationReceiver();
         return v;
     }
 
@@ -234,22 +245,19 @@
     public void onResume() {
         super.onResume();
 
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mBluetoothAdapter.getProfileProxy(getContext(), new MapServiceListener(),
-                BluetoothProfile.MAP_CLIENT);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_MESSAGE_SENT_SUCCESSFULLY);
-        intentFilter.addAction(ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
-        intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
-        intentFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
-        getContext().registerReceiver(mTransmissionStatusReceiver, intentFilter);
+        if (BluetoothConnectionPermissionChecker.isPermissionGranted(mActivity)) {
+            registerMapServiceListenerAndNotificationReceiver();
+        }
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        getContext().unregisterReceiver(mTransmissionStatusReceiver);
+
+        if (mTransmissionStatusReceiver != null) {
+            getContext().unregisterReceiver(mTransmissionStatusReceiver);
+            mTransmissionStatusReceiver = null;
+        }
     }
 
     private void getMessages() {
@@ -270,6 +278,20 @@
         }
     }
 
+    private void registerMapServiceListenerAndNotificationReceiver() {
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mBluetoothAdapter.getProfileProxy(getContext(), new MapServiceListener(),
+                BluetoothProfile.MAP_CLIENT);
+
+        mTransmissionStatusReceiver = new NotificationReceiver();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_MESSAGE_SENT_SUCCESSFULLY);
+        intentFilter.addAction(ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
+        intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
+        intentFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
+        getContext().registerReceiver(mTransmissionStatusReceiver, intentFilter);
+    }
+
     private void sendNewMsgOnClick(int msgType) {
         String messageToSend = "";
         switch (msgType) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index 799e569..4685b62 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -143,21 +143,30 @@
         }
     }
 
-    protected static void suspendToRamAndResume() throws Exception {
+    protected static void suspendToRamAndResume(boolean disableBackgroundUsersStart)
+            throws Exception {
         Log.d(TAG, "Emulate suspend to RAM and resume");
-        PowerManager powerManager = sContext.getSystemService(PowerManager.class);
-        // clear log
-        runShellCommand("logcat -b all -c");
-        runShellCommand("cmd car_service suspend");
-        // Check for suspend success
-        waitUntil("screen is still on after suspend",
-                SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+        try {
+            if (disableBackgroundUsersStart) {
+                runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false");
+            }
 
-        // Force turn off garage mode
-        runShellCommand("cmd car_service garage-mode off");
-        runShellCommand("cmd car_service resume");
-        waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
-                + InitialUserInfoRequestType.RESUME, 60_000);
+            PowerManager powerManager = sContext.getSystemService(PowerManager.class);
+            // clear log
+            runShellCommand("logcat -b all -c");
+            runShellCommand("cmd car_service suspend");
+            // Check for suspend success
+            waitUntil("screen is still on after suspend",
+                    SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+
+            // Force turn off garage mode
+            runShellCommand("cmd car_service garage-mode off");
+            runShellCommand("cmd car_service resume");
+            waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
+                    + InitialUserInfoRequestType.RESUME, 60_000);
+        } catch (Exception e) {
+            runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true");
+        }
     }
 
     /**
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
index 160e3fb..9b4f293 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
@@ -176,12 +176,13 @@
     }
 
     @Test
+    @FlakyTest(bugId = 190417819)
     public void testLockNow_safe() throws Exception {
         lockNowTest(/* safe= */ true);
     }
 
     @Test
-    @FlakyTest(bugId = 178475817)
+    @FlakyTest(bugId = 190417819)
     public void testLockNow_unsafe() throws Exception {
         lockNowTest(/* safe= */ false);
     }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
index cfa0d8a..eb331f5 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
@@ -16,6 +16,8 @@
 
 package android.car.apitest;
 
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -26,6 +28,7 @@
 import android.app.ActivityManager;
 import android.car.Car;
 import android.car.test.util.AndroidHelper;
+import android.car.testapi.BlockingUserLifecycleListener;
 import android.car.user.CarUserManager;
 import android.car.user.UserCreationResult;
 import android.car.user.UserRemovalResult;
@@ -58,8 +61,8 @@
 
     private static final String TAG = CarMultiUserTestBase.class.getSimpleName();
 
-    private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10_000);
-    private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10_000);
+    private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
+    private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
 
     private static final String PROP_STOP_BG_USERS_ON_SWITCH  = "fw.stop_bg_users_on_switch";
 
@@ -83,6 +86,9 @@
     public final void setMultiUserFixtures() throws Exception {
         Log.d(TAG, "setMultiUserFixtures() for " + mTestName.getMethodName());
 
+        // Make sure user doesn't stop on switch (otherwise test process would crash)
+        setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "0");
+
         mCarUserManager = getCarService(Car.CAR_USER_SERVICE);
         mUserManager = getContext().getSystemService(UserManager.class);
 
@@ -94,9 +100,6 @@
             }
         }, filter);
 
-        // Make sure user doesn't stop on switch (otherwise test process would crash)
-        setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "0");
-
         List<UserInfo> users = mUserManager.getAliveUsers();
 
         // Set current user
@@ -142,18 +145,15 @@
         mSetupFinished = true;
     }
 
-    @After
     public final void resetStopUserOnSwitch() throws Exception {
-        if (!mSetupFinished) return;
-
         setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "-1");
     }
 
     @After
     public final void cleanupUserState() throws Exception {
-        if (!mSetupFinished) return;
-
         try {
+            if (!mSetupFinished) return;
+
             int currentUserId = getCurrentUserId();
             int initialUserId = mInitialUser.id;
             if (currentUserId != initialUserId) {
@@ -177,6 +177,9 @@
             Log.e(TAG, "Caught exception on " + getTestName()
                     + " disconnectCarAndCleanupUserState()", e);
         }
+        finally {
+            resetStopUserOnSwitch();
+        }
     }
 
     @UserIdInt
@@ -252,14 +255,36 @@
     }
 
     protected void switchUser(@UserIdInt int userId) throws Exception {
-        Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
+        boolean waitForUserSwitchToComplete = true;
+        // If current user is the target user, no life cycle event is expected.
+        if (getCurrentUserId() == userId) waitForUserSwitchToComplete = false;
 
-        AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(userId);
-        UserSwitchResult result = future.get(SWITCH_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        Log.d(TAG, "Result: " + result);
+        Log.d(TAG, "registering listener for user switching");
+        BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(userId)
+                .setTimeout(SWITCH_USER_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+                .build();
+        mCarUserManager.addListener(Runnable::run, listener);
 
-        assertWithMessage("User %s switched in %sms. Result: %s", userId, SWITCH_USER_TIMEOUT_MS,
-                result).that(result.isSuccess()).isTrue();
+        try {
+            Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
+            AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(userId);
+            UserSwitchResult result = future.get(SWITCH_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            Log.d(TAG, "Result: " + result);
+
+            assertWithMessage("User %s switched in %sms. Result: %s", userId,
+                    SWITCH_USER_TIMEOUT_MS, result).that(result.isSuccess()).isTrue();
+
+            if (waitForUserSwitchToComplete) {
+                listener.waitForEvents();
+            }
+        } finally {
+            mCarUserManager.removeListener(listener);
+        }
+
+        Log.d(TAG, "User switch complete. User id: " + userId);
     }
 
     protected void removeUser(@UserIdInt int userId) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java
new file mode 100644
index 0000000..b643065
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.car.apitest;
+
+import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+// DO NOT ADD ANY TEST TO THIS CLASS
+// This class will have only one test testLifecycleListener.
+public final class CarUserManagerLifeCycleTest extends CarMultiUserTestBase {
+
+    private static final String TAG = CarUserManagerLifeCycleTest.class.getSimpleName();
+
+    private static final int PIN = 2345;
+
+    private static final int SWITCH_TIMEOUT_MS = 70_000;
+    // A large stop timeout is required as sometimes stop user broadcast takes a significantly
+    // long time to complete. This happen when there are multiple users starting/stopping in
+    // background which is the case in this test class.
+    private static final int STOP_TIMEOUT_MS = 600_000;
+
+    /**
+     * Stopping the user takes a while, even when calling force stop - change it to {@code false}
+     * if {@code testLifecycleListener} becomes flaky.
+     */
+    private static final boolean TEST_STOP_EVENTS = true;
+
+    private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
+    private static boolean sChangedMaxNumberUsers;
+
+    @BeforeClass
+    public static void setupMaxNumberOfUsers() {
+        int requiredUsers = 3; // system user, current user, 1 extra user
+        if (sMaxNumberUsersBefore < requiredUsers) {
+            sChangedMaxNumberUsers = true;
+            Log.i(TAG, "Increasing maximing number of users from " + sMaxNumberUsersBefore + " to "
+                    + requiredUsers);
+            setMaxSupportedUsers(requiredUsers);
+        }
+    }
+
+    @AfterClass
+    public static void restoreMaxNumberOfUsers() {
+        if (sChangedMaxNumberUsers) {
+            Log.i(TAG, "Restoring maximum number of users to " + sMaxNumberUsersBefore);
+            setMaxSupportedUsers(sMaxNumberUsersBefore);
+        }
+    }
+
+    @Test(timeout = 600_000)
+    public void testLifecycleListener() throws Exception {
+        int initialUserId = getCurrentUserId();
+        int newUserId = createUser().id;
+
+        BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(newUserId)
+                .setTimeout(SWITCH_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+                .build();
+
+        Log.d(TAG, "registering start listener: " + startListener);
+        AtomicBoolean executedRef = new AtomicBoolean();
+
+        Executor mExecutor = (r) -> {
+            executedRef.set(true);
+            r.run();
+        };
+        mCarUserManager.addListener(mExecutor, startListener);
+
+        // Switch while listener is registered
+        switchUser(newUserId);
+
+        List<UserLifecycleEvent> startEvents  = startListener.waitForEvents();
+        Log.d(TAG, "Received start events: " + startEvents);
+
+        // Make sure listener callback was executed in the proper threaqd
+        assertWithMessage("executed on executor").that(executedRef.get()).isTrue();
+
+        // Assert user ids
+        List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
+        Log.d(TAG, "Received expected events: " + expectedEvents);
+        for (UserLifecycleEvent event : expectedEvents) {
+            assertWithMessage("userId on event %s", event)
+                .that(event.getUserId()).isEqualTo(newUserId);
+            assertWithMessage("userHandle on event %s", event)
+                .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
+            if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+                assertWithMessage("previousUserId on event %s", event)
+                    .that(event.getPreviousUserId()).isEqualTo(initialUserId);
+                assertWithMessage("previousUserHandle on event %s", event)
+                    .that(event.getPreviousUserHandle().getIdentifier()).isEqualTo(initialUserId);
+            }
+        }
+
+        Log.d(TAG, "unregistering start listener: " + startListener);
+        mCarUserManager.removeListener(startListener);
+
+        BlockingUserLifecycleListener stopListener = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(newUserId)
+                .setTimeout(STOP_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED)
+                .build();
+
+        Log.d(TAG, "registering stop listener: " + stopListener);
+        mCarUserManager.addListener(mExecutor, stopListener);
+
+        // Switch back to the initial user
+        switchUser(initialUserId);
+
+        if (TEST_STOP_EVENTS) {
+            // Must force stop the user, otherwise it can take minutes for its process to finish
+            forceStopUser(newUserId);
+
+            List<UserLifecycleEvent> stopEvents = stopListener.waitForEvents();
+            Log.d(TAG, "stopEvents: " + stopEvents + "; all events on stop listener: "
+                    + stopListener.getAllReceivedEvents());
+
+            // Assert user ids
+            for (UserLifecycleEvent event : stopEvents) {
+                assertWithMessage("userId on %s", event)
+                    .that(event.getUserId()).isEqualTo(newUserId);
+                assertWithMessage("wrong userHandle on %s", event)
+                    .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
+            }
+        } else {
+            Log.w(TAG, "NOT testing user stop events");
+        }
+
+        // Make sure unregistered listener didn't receive any more events
+        List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
+        Log.d(TAG, "All start events: " + startEvents);
+        assertThat(allStartEvents).containsAtLeastElementsIn(startEvents).inOrder();
+
+        Log.d(TAG, "unregistering stop listener: " + stopListener);
+        mCarUserManager.removeListener(stopListener);
+    }
+
+    private static void forceStopUser(@UserIdInt int userId) throws RemoteException {
+        Log.i(TAG, "Force-stopping user " + userId);
+        IActivityManager am = ActivityManager.getService();
+        am.stopUser(userId, /* force=*/ true, /* listener= */ null);
+    }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index 0cdca04..d66e3dd 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -18,36 +18,22 @@
 import static android.car.test.util.UserTestingHelper.clearUserLockCredentials;
 import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
 import static android.car.test.util.UserTestingHelper.setUserLockCredentials;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
 
-import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.car.testapi.BlockingUserLifecycleListener;
 import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.content.pm.UserInfo;
-import android.os.RemoteException;
 import android.os.UserManager;
 import android.util.Log;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 public final class CarUserManagerTest extends CarMultiUserTestBase {
 
     private static final String TAG = CarUserManagerTest.class.getSimpleName();
@@ -55,13 +41,6 @@
     private static final int PIN = 2345;
 
     private static final int SWITCH_TIMEOUT_MS = 70_000;
-    private static final int STOP_TIMEOUT_MS = 300_000;
-
-    /**
-     * Stopping the user takes a while, even when calling force stop - change it to {@code false}
-     * if {@code testLifecycleListener} becomes flaky.
-     */
-    private static final boolean TEST_STOP_EVENTS = true;
 
     private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
     private static boolean sChangedMaxNumberUsers;
@@ -85,106 +64,10 @@
         }
     }
 
-    @FlakyTest //TODO(b/183519890): STOPSHIP switch is getting wrong previous id
-    @Test
-    public void testLifecycleListener() throws Exception {
-        int initialUserId = getCurrentUserId();
-        int newUserId = createUser().id;
-
-        BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
-                .forSpecificEvents()
-                .forUser(newUserId)
-                .setTimeout(SWITCH_TIMEOUT_MS)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
-                .build();
-
-        Log.d(TAG, "registering start listener: " + startListener);
-        AtomicBoolean executedRef = new AtomicBoolean();
-
-        Executor mExecutor = (r) -> {
-            executedRef.set(true);
-            r.run();
-        };
-        mCarUserManager.addListener(mExecutor, startListener);
-
-        // Switch while listener is registered
-        switchUser(newUserId);
-
-        List<UserLifecycleEvent> startEvents  = startListener.waitForEvents();
-        Log.d(TAG, "Received start events: " + startEvents);
-
-        // Make sure listener callback was executed in the proper threaqd
-        assertWithMessage("executed on executor").that(executedRef.get()).isTrue();
-
-        // Assert user ids
-        List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
-        Log.d(TAG, "Received expected events: " + expectedEvents);
-        for (UserLifecycleEvent event : expectedEvents) {
-            assertWithMessage("userId on event %s", event)
-                .that(event.getUserId()).isEqualTo(newUserId);
-            assertWithMessage("userHandle on event %s", event)
-                .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
-            if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
-                assertWithMessage("previousUserId on event %s", event)
-                    .that(event.getPreviousUserId()).isEqualTo(initialUserId);
-                assertWithMessage("previousUserHandle on event %s", event)
-                    .that(event.getPreviousUserHandle().getIdentifier()).isEqualTo(initialUserId);
-            }
-        }
-
-        Log.d(TAG, "unregistering start listener: " + startListener);
-        mCarUserManager.removeListener(startListener);
-
-        BlockingUserLifecycleListener stopListener = BlockingUserLifecycleListener
-                .forSpecificEvents()
-                .forUser(newUserId)
-                .setTimeout(STOP_TIMEOUT_MS)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
-                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED)
-                .build();
-
-        Log.d(TAG, "registering stop listener: " + stopListener);
-        mCarUserManager.addListener(mExecutor, stopListener);
-
-        // Switch back to the initial user
-        switchUser(initialUserId);
-
-        if (TEST_STOP_EVENTS) {
-            // Must force stop the user, otherwise it can take minutes for its process to finish
-            forceStopUser(newUserId);
-
-            List<UserLifecycleEvent> stopEvents = stopListener.waitForEvents();
-            Log.d(TAG, "stopEvents: " + stopEvents + "; all events on stop listener: "
-                    + stopListener.getAllReceivedEvents());
-
-            // Assert user ids
-            for (UserLifecycleEvent event : stopEvents) {
-                assertWithMessage("userId on %s", event)
-                    .that(event.getUserId()).isEqualTo(newUserId);
-                assertWithMessage("wrong userHandle on %s", event)
-                    .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
-            }
-        } else {
-            Log.w(TAG, "NOT testing user stop events");
-        }
-
-        // Make sure unregistered listener didn't receive any more events
-        List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
-        Log.d(TAG, "All start events: " + startEvents);
-        assertThat(allStartEvents).containsAtLeastElementsIn(startEvents).inOrder();
-
-        Log.d(TAG, "unregistering stop listener: " + stopListener);
-        mCarUserManager.removeListener(stopListener);
-    }
-
     /**
      * Tests resume behavior when current user is ephemeral guest, a new guest user should be
      * created and switched to.
      */
-    @FlakyTest //TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
     @Test
     public void testGuestUserResumeToNewGuestUser() throws Exception {
         // Create new guest user
@@ -213,7 +96,7 @@
         mCarUserManager.addListener(Runnable::run, listener2);
 
         // Emulate suspend to RAM
-        suspendToRamAndResume();
+        suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
         UserLifecycleEvent event = listener2.waitForEvents().get(0);
 
         int newGuestId = event.getUserId();
@@ -233,7 +116,6 @@
      * Tests resume behavior when current user is guest but with secured lock screen,
      * resume to same guest user.
      */
-    @FlakyTest //TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
     @Test
     public void testSecuredGuestUserResumeToSameUser() throws Exception {
         // Create new guest user
@@ -257,7 +139,7 @@
         setUserLockCredentials(guestUserId, PIN);
         try {
             // Emulate suspend to RAM
-            suspendToRamAndResume();
+            suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
 
             assertWithMessage("current user remains guest user (%s)", guestUser)
                     .that(getCurrentUserId()).isEqualTo(guestUserId);
@@ -269,7 +151,6 @@
     /**
      * Tests resume behavior when current user is persistent user.
      */
-    @FlakyTest // TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
     @Test
     public void testPersistentUserResumeToUser() throws Exception {
         int newUserId = createUser().id;
@@ -284,7 +165,7 @@
         listener.waitForEvents();
 
         // Emulate suspend to RAM
-        suspendToRamAndResume();
+        suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
 
         listener.waitForEvents();
         assertWithMessage("current user remains new user (%s)", newUserId)
@@ -292,10 +173,4 @@
 
         mCarUserManager.removeListener(listener);
     }
-
-    private static void forceStopUser(@UserIdInt int userId) throws RemoteException {
-        Log.i(TAG, "Force-stopping user " + userId);
-        IActivityManager am = ActivityManager.getService();
-        am.stopUser(userId, /* force=*/ true, /* listener= */ null);
-    }
 }
diff --git a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
index 484197c..8306420 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
@@ -285,11 +285,12 @@
 
         carZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
 
-        ArgumentCaptor<List<AudioFocusInfo>> focusHoldersCaptor = ArgumentCaptor.forClass(
-                List.class);
-        verify(mMockCarFocusCallback).onFocusChange(eq(PRIMARY_ZONE_ID),
+        ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor =
+                ArgumentCaptor.forClass(SparseArray.class);
+        verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}),
                 focusHoldersCaptor.capture());
-        assertThat(focusHoldersCaptor.getValue()).containsExactly(audioFocusInfo);
+        assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID))
+                .containsExactly(audioFocusInfo);
     }
 
     @Test
@@ -299,11 +300,11 @@
 
         carZonesAudioFocus.onAudioFocusAbandon(audioFocusInfo);
 
-        ArgumentCaptor<List<AudioFocusInfo>> focusHoldersCaptor = ArgumentCaptor.forClass(
-                List.class);
-        verify(mMockCarFocusCallback).onFocusChange(eq(PRIMARY_ZONE_ID),
+        ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor =
+                ArgumentCaptor.forClass(SparseArray.class);
+        verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}),
                 focusHoldersCaptor.capture());
-        assertThat(focusHoldersCaptor.getValue()).isEmpty();
+        assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID)).isEmpty();
     }
 
     private AudioFocusInfo generateMediaRequestForPrimaryZone() {
diff --git a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
index b1f9e0c..ca3598a 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
@@ -110,7 +110,8 @@
         CarLocalServices.addService(CarUserService.class, mCarUserServiceMock);
         CarLocalServices.removeServiceForTest(SystemInterface.class);
         CarLocalServices.addService(SystemInterface.class, mSystemInterfaceMock);
-        doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock).startAllBackgroundUsers();
+        doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock)
+                .startAllBackgroundUsersInGarageMode();
         doNothing().when(mSystemInterfaceMock)
                 .sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
     }
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
index 5ef4086..499a95f 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
@@ -96,7 +96,8 @@
     @Test
     public void test_backgroundUsersStopedOnGarageModeCancel() throws Exception {
         ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103));
-        when(mCarUserService.startAllBackgroundUsers()).thenReturn(userToStartInBackground);
+        when(mCarUserService.startAllBackgroundUsersInGarageMode())
+                .thenReturn(userToStartInBackground);
         mGarageMode.enterGarageMode(/* future= */ null);
         CountDownLatch latch = new CountDownLatch(3); // 3 for three users
         mockCarUserServiceStopUserCall(getEventListener(), latch);
@@ -104,10 +105,10 @@
         mGarageMode.cancel();
 
         waitForHandlerThreadToFinish(latch);
-        verify(mCarUserService).startAllBackgroundUsers();
-        verify(mCarUserService).stopBackgroundUser(101);
-        verify(mCarUserService).stopBackgroundUser(102);
-        verify(mCarUserService).stopBackgroundUser(103);
+        verify(mCarUserService).startAllBackgroundUsersInGarageMode();
+        verify(mCarUserService).stopBackgroundUserInGagageMode(101);
+        verify(mCarUserService).stopBackgroundUserInGagageMode(102);
+        verify(mCarUserService).stopBackgroundUserInGagageMode(103);
     }
 
     @Test
@@ -140,7 +141,7 @@
         doAnswer(inv -> {
             latch.countDown();
             return userToStartInBackground;
-        }).when(mCarUserService).startAllBackgroundUsers();
+        }).when(mCarUserService).startAllBackgroundUsersInGarageMode();
 
         return latch;
     }
@@ -161,7 +162,7 @@
             listener.onEvent(new UserLifecycleEvent(
                     CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED, userId));
             return null;
-        }).when(mCarUserService).stopBackgroundUser(anyInt());
+        }).when(mCarUserService).stopBackgroundUserInGagageMode(anyInt());
     }
 }
 
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
new file mode 100644
index 0000000..5373ceb
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.audio;
+
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_QUAD;
+import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioGain;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioDeviceInfoTest {
+
+    private static final String TEST_ADDRESS = "test address";
+    private static final int MIN_GAIN = 0;
+    private static final int MAX_GAIN = 100;
+    private static final int DEFAULT_GAIN = 50;
+    private static final int STEP_SIZE = 2;
+
+    @Test
+    public void constructor_requiresNonNullGain() {
+        AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(audioDeviceInfo.getPort()).thenReturn(null);
+
+        assertThrows(NullPointerException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresJointModeGain() {
+        AudioGain gainWithChannelMode = new GainBuilder().setMode(AudioGain.MODE_CHANNELS).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(NullPointerException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresMaxGainLargerThanMin() {
+        AudioGain gainWithChannelMode = new GainBuilder().setMaxValue(10).setMinValue(20).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresDefaultGainLargerThanMin() {
+        AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(10).setMinValue(
+                20).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresDefaultGainSmallerThanMax() {
+        AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(15).setMaxValue(
+                10).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresGainStepSizeFactorOfRange() {
+        AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void constructor_requiresGainStepSizeFactorOfRangeToDefault() {
+        AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).setMaxValue(98).build();
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+                new AudioGain[]{gainWithChannelMode});
+
+        assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+    }
+
+    @Test
+    public void getSampleRate_withMultipleSampleRates_returnsMax() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        int[] sampleRates = new int[]{48000, 96000, 16000, 8000};
+        when(audioDeviceInfo.getSampleRates()).thenReturn(sampleRates);
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        int sampleRate = info.getSampleRate();
+
+        assertThat(sampleRate).isEqualTo(96000);
+    }
+
+    @Test
+    public void getSampleRate_withNullSampleRate_returnsDefault() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        when(audioDeviceInfo.getSampleRates()).thenReturn(null);
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        int sampleRate = info.getSampleRate();
+
+        assertThat(sampleRate).isEqualTo(CarAudioDeviceInfo.DEFAULT_SAMPLE_RATE);
+    }
+
+    @Test
+    public void getAudioDevicePort_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getAudioDevicePort()).isEqualTo(audioDeviceInfo.getPort());
+    }
+
+    @Test
+    public void getAddress_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getAddress()).isEqualTo(TEST_ADDRESS);
+    }
+
+    @Test
+    public void getMaxGain_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getMaxGain()).isEqualTo(MAX_GAIN);
+    }
+
+    @Test
+    public void getMinGain_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getMinGain()).isEqualTo(MIN_GAIN);
+    }
+
+    @Test
+    public void getDefaultGain_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getDefaultGain()).isEqualTo(DEFAULT_GAIN);
+    }
+
+    @Test
+    public void getStepValue_returnsValueFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getStepValue()).isEqualTo(STEP_SIZE);
+    }
+
+    @Test
+    public void getChannelCount_withNoChannelMasks_returnsOne() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        int channelCount = info.getChannelCount();
+
+        assertThat(channelCount).isEqualTo(1);
+    }
+
+    @Test
+    public void getChannelCount_withMultipleChannels_returnsHighestCount() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        when(audioDeviceInfo.getChannelMasks()).thenReturn(new int[]{CHANNEL_OUT_STEREO,
+                CHANNEL_OUT_QUAD, CHANNEL_OUT_MONO});
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        int channelCount = info.getChannelCount();
+
+        assertThat(channelCount).isEqualTo(4);
+    }
+
+    @Test
+    public void getAudioDeviceInfo_returnsConstructorParameter() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getAudioDeviceInfo()).isEqualTo(audioDeviceInfo);
+    }
+
+    @Test
+    public void getEncodingFormat_returnsPCM16() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+        assertThat(info.getEncodingFormat()).isEqualTo(ENCODING_PCM_16BIT);
+    }
+
+    @Test
+    public void getAudioGain_returnsGainFromDeviceInfo() {
+        AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+        CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+        AudioGain expectedGain = audioDeviceInfo.getPort().gains()[0];
+
+        assertThat(info.getAudioGain()).isEqualTo(expectedGain);
+    }
+
+    private AudioDeviceInfo getMockAudioDeviceInfo() {
+        AudioGain mockGain = new GainBuilder().build();
+        return getMockAudioDeviceInfo(new AudioGain[]{mockGain});
+    }
+
+    private AudioDeviceInfo getMockAudioDeviceInfo(AudioGain[] gains) {
+        AudioDeviceInfo mockInfo = mock(AudioDeviceInfo.class);
+        AudioDevicePort mockPort = mock(AudioDevicePort.class);
+        when(mockInfo.getPort()).thenReturn(mockPort);
+        when(mockPort.gains()).thenReturn(gains);
+        when(mockInfo.getAddress()).thenReturn(TEST_ADDRESS);
+        return mockInfo;
+    }
+
+    private static class GainBuilder {
+        int mMode = AudioGain.MODE_JOINT;
+        int mMaxValue = MAX_GAIN;
+        int mMinValue = MIN_GAIN;
+        int mDefaultValue = DEFAULT_GAIN;
+        int mStepSize = STEP_SIZE;
+
+        GainBuilder setMode(int mode) {
+            mMode = mode;
+            return this;
+        }
+
+        GainBuilder setMaxValue(int maxValue) {
+            mMaxValue = maxValue;
+            return this;
+        }
+
+        GainBuilder setMinValue(int minValue) {
+            mMinValue = minValue;
+            return this;
+        }
+
+        GainBuilder setDefaultValue(int defaultValue) {
+            mDefaultValue = defaultValue;
+            return this;
+        }
+
+        GainBuilder setStepSize(int stepSize) {
+            mStepSize = stepSize;
+            return this;
+        }
+
+        AudioGain build() {
+            AudioGain mockGain = mock(AudioGain.class);
+            when(mockGain.mode()).thenReturn(mMode);
+            when(mockGain.maxValue()).thenReturn(mMaxValue);
+            when(mockGain.minValue()).thenReturn(mMinValue);
+            when(mockGain.defaultValue()).thenReturn(mDefaultValue);
+            when(mockGain.stepValue()).thenReturn(mStepSize);
+            return mockGain;
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
index 5fa1bfd..4a0171b 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
@@ -50,6 +50,7 @@
 public final class CarDuckingTest {
     private static final int PRIMARY_ZONE_ID = 0;
     private static final int PASSENGER_ZONE_ID = 1;
+    private static final int[] ONE_ZONE_CHANGE = new int[]{PRIMARY_ZONE_ID};
     private static final int REAR_ZONE_ID = 2;
     private static final String PRIMARY_MEDIA_ADDRESS = "primary_media";
     private static final String PRIMARY_NAVIGATION_ADDRESS = "primary_navigation_address";
@@ -59,18 +60,22 @@
     private final AudioFocusInfo mMediaFocusInfo = generateAudioFocusInfoForUsage(USAGE_MEDIA);
     private final AudioFocusInfo mNavigationFocusInfo =
             generateAudioFocusInfoForUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+    private final SparseArray<List<AudioFocusInfo>> mMediaFocusHolders = new SparseArray<>();
+    private final SparseArray<List<AudioFocusInfo>> mMediaNavFocusHolders = new SparseArray<>();
 
     @Mock
     private AudioControlWrapper mMockAudioControlWrapper;
 
     @Captor
-    private ArgumentCaptor<CarDuckingInfo> mCarDuckingInfoCaptor;
+    private ArgumentCaptor<List<CarDuckingInfo>> mCarDuckingInfosCaptor;
 
     private CarDucking mCarDucking;
 
     @Before
     public void setUp() {
         mCarDucking = new CarDucking(mCarAudioZones, mMockAudioControlWrapper);
+        mMediaFocusHolders.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo));
+        mMediaNavFocusHolders.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo, mNavigationFocusInfo));
     }
 
     @Test
@@ -90,9 +95,7 @@
 
     @Test
     public void onFocusChange_forPrimaryZone_updatesUsagesHoldingFocus() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
-
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
 
         SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
         assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mUsagesHoldingFocus)
@@ -101,9 +104,7 @@
 
     @Test
     public void onFocusChange_forPrimaryZone_doesNotUpdateSecondaryZones() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
-
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
 
         SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
         assertThat(newDuckingInfo.get(PASSENGER_ZONE_ID).mUsagesHoldingFocus).isEmpty();
@@ -112,9 +113,7 @@
 
     @Test
     public void onFocusChange_withMultipleFocusHolders_updatesAddressesToDuck() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
-
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
 
         SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
         assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToDuck)
@@ -123,11 +122,9 @@
 
     @Test
     public void onFocusChange_withDuckedDevices_updatesAddressesToUnduck() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
 
-        List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
 
         SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
         assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToUnduck)
@@ -136,41 +133,48 @@
 
     @Test
     public void onFocusChange_notifiesHalOfUsagesHoldingFocus() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
 
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
-
-        ArgumentCaptor<CarDuckingInfo> captor = ArgumentCaptor.forClass(CarDuckingInfo.class);
-        verify(mMockAudioControlWrapper).onDevicesToDuckChange(captor.capture());
-        int[] usagesHoldingFocus = captor.getValue().mUsagesHoldingFocus;
+        verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+        int[] usagesHoldingFocus = mCarDuckingInfosCaptor.getValue().get(0).mUsagesHoldingFocus;
         assertThat(usagesHoldingFocus).asList().containsExactly(USAGE_MEDIA);
     }
 
     @Test
     public void onFocusChange_notifiesHalOfAddressesToDuck() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
 
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
-
-        verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
-        List<String> addressesToDuck = mCarDuckingInfoCaptor.getValue().mAddressesToDuck;
+        verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+        List<String> addressesToDuck = mCarDuckingInfosCaptor.getValue().get(0).mAddressesToDuck;
         assertThat(addressesToDuck).containsExactly(PRIMARY_MEDIA_ADDRESS);
     }
 
     @Test
     public void onFocusChange_notifiesHalOfAddressesToUnduck() {
-        List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
 
-        List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
-        mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
+        mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
 
         verify(mMockAudioControlWrapper, times(2))
-                .onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
-        List<String> addressesToUnduck = mCarDuckingInfoCaptor.getValue().mAddressesToUnduck;
+                .onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+        List<String> addressesToUnduck = mCarDuckingInfosCaptor.getValue().get(0)
+                .mAddressesToUnduck;
         assertThat(addressesToUnduck).containsExactly(PRIMARY_MEDIA_ADDRESS);
     }
 
+    @Test
+    public void onFocusChange_withMultipleZones_notifiesForEachZone() {
+        int[] zoneIds = new int[]{PRIMARY_ZONE_ID, PASSENGER_ZONE_ID};
+        SparseArray<List<AudioFocusInfo>> focusChanges = new SparseArray<>();
+        focusChanges.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo));
+        focusChanges.put(PASSENGER_ZONE_ID, List.of(mNavigationFocusInfo));
+
+        mCarDucking.onFocusChange(zoneIds, focusChanges);
+
+        verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+        assertThat(mCarDuckingInfosCaptor.getValue().size()).isEqualTo(zoneIds.length);
+    }
+
     private AudioFocusInfo generateAudioFocusInfoForUsage(int usage) {
         AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage).build();
         return new AudioFocusInfo(attributes, 0, "client_id", "package.name",
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
index 099474c..685a348 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
@@ -20,6 +20,9 @@
 import static android.media.AudioManager.AUDIOFOCUS_GAIN;
 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -39,6 +42,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -179,7 +183,15 @@
 
         mCarZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
 
-        verify(mMockCarFocusCallback).onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+        ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> captor =
+                ArgumentCaptor.forClass(SparseArray.class);
+        verify(mMockCarFocusCallback)
+                .onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}), captor.capture());
+        SparseArray<List<AudioFocusInfo>> results = captor.getValue();
+        assertWithMessage("Number of lists returned in sparse array")
+                .that(results.size()).isEqualTo(1);
+        assertWithMessage("Focus holders for primary zone")
+                .that(results.get(PRIMARY_ZONE_ID)).isEqualTo(focusHolders);
     }
 
     @Test
@@ -206,6 +218,18 @@
                 .setRestrictFocus(false);
     }
 
+    @Test
+    public void setRestrictFocus_notifiesFocusCallbackForAllZones() {
+        mCarZonesAudioFocus.setRestrictFocus(false);
+
+        ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> captor =
+                ArgumentCaptor.forClass(SparseArray.class);
+        int[] expectedZoneIds = new int[]{PRIMARY_ZONE_ID, SECONDARY_ZONE_ID};
+        verify(mMockCarFocusCallback).onFocusChange(eq(expectedZoneIds), captor.capture());
+        assertWithMessage("Number of focus holder lists")
+                .that(captor.getValue().size()).isEqualTo(2);
+    }
+
     private static SparseArray<CarAudioZone> generateAudioZones() {
         SparseArray<CarAudioZone> zones = new SparseArray<>();
         zones.put(PRIMARY_ZONE_ID, new CarAudioZone(PRIMARY_ZONE_ID, "Primary zone"));
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
index 4a99b91..cc914ca 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
@@ -182,7 +182,7 @@
         CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
                 new ArrayList<>(), new int[0]);
 
-        mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
 
         ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
         verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -195,7 +195,7 @@
         CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
                 new ArrayList<>(), new int[]{USAGE_MEDIA, USAGE_NOTIFICATION});
 
-        mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
 
         ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
         verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -212,7 +212,7 @@
         CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID,
                 Arrays.asList(mediaAddress, navigationAddress), new ArrayList<>(), new int[0]);
 
-        mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
 
         ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
         verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -228,7 +228,7 @@
         CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
                 Arrays.asList(notificationAddress, callAddress), new int[0]);
 
-        mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
 
         ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
         verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -242,7 +242,7 @@
         CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
                 new ArrayList<>(), new int[0]);
 
-        mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
 
         ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
         verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -251,6 +251,22 @@
     }
 
     @Test
+    public void onDevicesToDuckChange_multipleZones_passesADuckingInfoPerZone() throws Exception {
+        CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
+                new ArrayList<>(), new int[0]);
+        CarDuckingInfo secondaryCarDuckingInfo = new CarDuckingInfo(SECONDARY_ZONE_ID,
+                new ArrayList<>(), new ArrayList<>(), new int[0]);
+
+        mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo,
+                secondaryCarDuckingInfo));
+
+        ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
+        verify(mAudioControl).onDevicesToDuckChange(captor.capture());
+        assertWithMessage("Number of ducking infos passed along")
+                .that(captor.getValue().length).isEqualTo(2);
+    }
+
+    @Test
     public void linkToDeath_callsBinder() throws Exception {
         mAudioControlWrapperAidl.linkToDeath(null);
 
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 364a4ca..bc6c7ea 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -548,15 +548,15 @@
 
         when(mMockedIActivityManager.startUserInBackground(user2)).thenReturn(true);
         when(mMockedIActivityManager.unlockUser(user2, null, null, null)).thenReturn(true);
-        assertThat(mCarUserService.startAllBackgroundUsers()).containsExactly(user2);
+        assertThat(mCarUserService.startAllBackgroundUsersInGarageMode()).containsExactly(user2);
         sendUserUnlockedEvent(user2);
         assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
 
         when(mMockedIActivityManager.stopUser(user2, true, null))
                 .thenReturn(ActivityManager.USER_OP_SUCCESS);
         // should not stop the current fg user
-        assertThat(mCarUserService.stopBackgroundUser(user3)).isFalse();
-        assertThat(mCarUserService.stopBackgroundUser(user2)).isTrue();
+        assertThat(mCarUserService.stopBackgroundUserInGagageMode(user3)).isFalse();
+        assertThat(mCarUserService.stopBackgroundUserInGagageMode(user2)).isTrue();
         assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
         assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
     }
@@ -645,7 +645,8 @@
         mockStopUserWithDelayedLocking(
                 UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
 
-        assertThat(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM)).isFalse();
+        assertThat(mCarUserService.stopBackgroundUserInGagageMode(UserHandle.USER_SYSTEM))
+                .isFalse();
     }
 
     @Test
@@ -653,7 +654,7 @@
         int userId = 101;
         mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
 
-        assertThat(mCarUserService.stopBackgroundUser(userId)).isFalse();
+        assertThat(mCarUserService.stopBackgroundUserInGagageMode(userId)).isFalse();
     }
 
     @Test