Added audio mirroring device parsing

Added audio mirroring device parsing from car audio configuration file.
Moved the device management to car audio mirror request handler, instead
of car audio service.

Bug: 265973263
Test: atest com.android.car.audio
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9975373d062be89bf5ca59229f988b395d738545)
Merged-In: Ia77bb97d23e661d532b8bfeae44b3bd37c6911b4

Change-Id: Ic26b6b3668e068d994611a392d11f2eb6a8d9949
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
index 18231e1..35a9281 100644
--- a/service/src/com/android/car/audio/CarAudioDynamicRouting.java
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -129,14 +129,15 @@
     }
 
     public static void setupAudioDynamicRoutingForMirrorDevice(
-            AudioPolicy.Builder mirrorPolicyBuilder, CarAudioDeviceInfo mirrorDevice) {
-        AudioFormat mixFormat = createMixFormatFromDevice(mirrorDevice);
+            AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos) {
+        for (int index = 0; index < audioDeviceInfos.size(); index++) {
+            AudioFormat mixFormat = createMixFormatFromDevice(audioDeviceInfos.get(index));
+            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
+            mixingRuleBuilder.addRule(CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA),
+                    AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
 
-        AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
-        mixingRuleBuilder.addRule(CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA),
-                AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
-
-        addMix(mirrorPolicyBuilder, mirrorDevice, mixFormat, mixingRuleBuilder);
+            addMix(mirrorPolicyBuilder, audioDeviceInfos.get(index), mixFormat, mixingRuleBuilder);
+        }
     }
 
     private static AudioFormat createMixFormatFromDevice(CarAudioDeviceInfo mirrorDevice) {
diff --git a/service/src/com/android/car/audio/CarAudioMirrorRequestHandler.java b/service/src/com/android/car/audio/CarAudioMirrorRequestHandler.java
index d44e893..0c7fbb5 100644
--- a/service/src/com/android/car/audio/CarAudioMirrorRequestHandler.java
+++ b/service/src/com/android/car/audio/CarAudioMirrorRequestHandler.java
@@ -24,6 +24,7 @@
 import android.car.builtin.util.Slogf;
 import android.car.media.CarAudioManager;
 import android.car.media.IAudioZonesMirrorStatusCallback;
+import android.media.AudioDeviceInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteCallbackList;
@@ -41,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -63,7 +65,7 @@
     private final RemoteCallbackList<IAudioZonesMirrorStatusCallback>
             mAudioZonesMirrorStatusCallbacks = new RemoteCallbackList<>();
     @GuardedBy("mLock")
-    private String mMirrorDeviceAddress;
+    private final List<CarAudioDeviceInfo> mMirrorDevices = new ArrayList<>();
     @GuardedBy("mLock")
     private final SparseLongArray mZonesToMirrorRequestId = new SparseLongArray();
 
@@ -92,15 +94,29 @@
 
     boolean isMirrorAudioEnabled() {
         synchronized (mLock) {
-            return mMirrorDeviceAddress != null;
+            return !mMirrorDevices.isEmpty();
         }
     }
 
-    void setMirrorDeviceAddress(String deviceAddress) {
-        Objects.requireNonNull(deviceAddress, "Mirror device address can not be null");
+    void setMirrorDeviceInfos(List<CarAudioDeviceInfo> mirroringDevices) {
+        Objects.requireNonNull(mirroringDevices, "Mirror devices can not be null");
 
         synchronized (mLock) {
-            mMirrorDeviceAddress = deviceAddress;
+            mMirrorDevices.clear();
+            mMirrorDevices.addAll(mirroringDevices);
+        }
+    }
+
+    List<CarAudioDeviceInfo> getMirroringDeviceInfos() {
+        synchronized (mLock) {
+            return List.copyOf(mMirrorDevices);
+        }
+    }
+
+    @Nullable AudioDeviceInfo getAudioDeviceInfo() {
+        //TODO (b/265973263): Replace 0 index with a request id indexing scheme
+        synchronized (mLock) {
+            return mMirrorDevices.isEmpty() ? null : mMirrorDevices.get(0).getAudioDeviceInfo();
         }
     }
 
@@ -219,6 +235,23 @@
         return CarServiceUtils.toIntArray(newConfig);
     }
 
+    long getUniqueRequestId() {
+        return mRequestIdGenerator.generateUniqueRequestId();
+    }
+
+    long getRequestIdForAudioZone(int audioZoneId) {
+        synchronized (mLock) {
+            return mZonesToMirrorRequestId.get(audioZoneId, INVALID_REQUEST_ID);
+        }
+    }
+
+    public void verifyValidRequestId(long requestId) {
+        synchronized (mLock) {
+            Preconditions.checkArgument(mRequestIdToZones.containsKey(requestId),
+                    "Mirror request id " + requestId + " is not valid");
+        }
+    }
+
     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     void dump(IndentingPrintWriter writer) {
         writer.printf("Is audio mirroring enabled? %s\n", isMirrorAudioEnabled() ? "Yes" : "No");
@@ -228,7 +261,8 @@
         writer.increaseIndent();
         int registeredCount = mAudioZonesMirrorStatusCallbacks.getRegisteredCallbackCount();
         synchronized (mLock) {
-            writer.printf("Mirroring device info: %s\n", mMirrorDeviceAddress);
+            writer.println("Mirroring device info:");
+            dumpMirrorDeviceInfosLocked(writer);
             writer.printf("Registered callback count: %d\n", registeredCount);
             writer.println("Mirroring configurations:");
             writer.increaseIndent();
@@ -249,20 +283,14 @@
         writer.decreaseIndent();
     }
 
-    long getUniqueRequestId() {
-        return mRequestIdGenerator.generateUniqueRequestId();
-    }
-
-    long getRequestIdForAudioZone(int audioZoneId) {
-        synchronized (mLock) {
-            return mZonesToMirrorRequestId.get(audioZoneId, INVALID_REQUEST_ID);
-        }
-    }
-
-    public void verifyValidRequestId(long requestId) {
-        synchronized (mLock) {
-            Preconditions.checkArgument(mRequestIdToZones.containsKey(requestId),
-                    "Mirror request id " + requestId + " is not valid");
+    @GuardedBy("mLock")
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
+    private void dumpMirrorDeviceInfosLocked(IndentingPrintWriter writer) {
+        for (int index = 0; index < mMirrorDevices.size(); index++) {
+            writer.printf("Mirror device[%d]\n", index);
+            writer.increaseIndent();
+            mMirrorDevices.get(index).dump(writer);
+            writer.decreaseIndent();
         }
     }
 }
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 5f25b1a..3372260 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -276,8 +276,6 @@
     };
 
     private AudioPolicy mAudioPolicy;
-    @GuardedBy("mImplLock")
-    private CarAudioDeviceInfo mCarAudioMirrorDeviceInfo;
     private CarZonesAudioFocus mFocusHandler;
     private String mCarAudioConfigurationPath;
     private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
@@ -1275,7 +1273,7 @@
             int userId = getUserIdForZone(zoneId);
             CarAudioZone audioZone = getCarAudioZone(zoneId);
             boolean enabled = setupMirrorDeviceForUserIdLocked(userId, audioZone,
-                    mCarAudioMirrorDeviceInfo.getAudioDeviceInfo());
+                    mCarAudioMirrorRequestHandler.getAudioDeviceInfo());
             if (!enabled) {
                 succeeded = false;
                 Slogf.w(TAG, "setupAudioRoutingForUserInMirrorDeviceLocked failed for zone "
@@ -1334,11 +1332,7 @@
             mAudioZoneIdToOccupantZoneIdMapping =
                     zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
             SparseArray<CarAudioZone> zones = zonesHelper.loadAudioZones();
-            mCarAudioMirrorDeviceInfo = zonesHelper.getMirrorDeviceInfo();
-            if (mCarAudioMirrorDeviceInfo != null) {
-                mCarAudioMirrorRequestHandler.setMirrorDeviceAddress(
-                        mCarAudioMirrorDeviceInfo.getAddress());
-            }
+            mCarAudioMirrorRequestHandler.setMirrorDeviceInfos(zonesHelper.getMirrorDeviceInfos());
             mCarAudioContext = zonesHelper.getCarAudioContext();
             return zones;
         } catch (IOException | XmlPullParserException e) {
@@ -1470,12 +1464,13 @@
 
     @GuardedBy("mImplLock")
     private void setupMirrorDevicePolicyLocked(AudioPolicy.Builder mirrorPolicyBuilder) {
-        if (mCarAudioMirrorDeviceInfo == null) {
+        if (mCarAudioMirrorRequestHandler.isMirrorAudioEnabled()) {
+            Slogf.i(TAG, "setupMirrorDevicePolicyLocked Audio mirroring is not enabled");
             return;
         }
 
         CarAudioDynamicRouting.setupAudioDynamicRoutingForMirrorDevice(mirrorPolicyBuilder,
-                mCarAudioMirrorDeviceInfo);
+                mCarAudioMirrorRequestHandler.getMirroringDeviceInfos());
     }
 
     @GuardedBy("mImplLock")
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index d0cb9ff..1fce3e5 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -56,7 +56,7 @@
 /**
  * A helper class loads all audio zones from the configuration XML file.
  */
-/* package */ class CarAudioZonesHelper {
+/* package */ final class CarAudioZonesHelper {
     private static final String NAMESPACE = null;
     private static final String TAG_ROOT = "carAudioConfiguration";
 
@@ -91,6 +91,8 @@
     private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId";
     private static final String TAG_INPUT_DEVICES = "inputDevices";
     private static final String TAG_INPUT_DEVICE = "inputDevice";
+    private static final String TAG_MIRRORING_DEVICES = "mirroringDevices";
+    private static final String TAG_MIRRORING_DEVICE = "mirroringDevice";
     private static final int INVALID_VERSION = -1;
     private static final int SUPPORTED_VERSION_1 = 1;
     private static final int SUPPORTED_VERSION_2 = 2;
@@ -117,6 +119,7 @@
     private final boolean mUseCarVolumeGroupMute;
     private final boolean mUseCoreAudioVolume;
     private final boolean mUseCoreAudioRouting;
+    private final List<CarAudioDeviceInfo> mMirroringDevices = new ArrayList<>();
 
     private final ArrayMap<String, Integer> mContextNameToId = new ArrayMap<>();
     private CarAudioContext mCarAudioContext;
@@ -206,6 +209,8 @@
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_OEM_CONTEXTS.equals(parser.getName())) {
                 parseCarAudioContexts(parser);
+            } else if (TAG_MIRRORING_DEVICES.equals(parser.getName())) {
+                parseMirroringDevices(parser);
             } else if (TAG_AUDIO_ZONES.equals(parser.getName())) {
                 loadCarAudioContexts();
                 return parseAudioZones(parser);
@@ -217,6 +222,37 @@
                 "", TAG_AUDIO_ZONES);
     }
 
+    private void parseMirroringDevices(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (isVersionLessThanThree()) {
+            throw new IllegalStateException(
+                    TAG_MIRRORING_DEVICES + " are not supported in car_audio_configuration.xml"
+                            + " version " + mCurrentVersion + ". Must be at least version "
+                            + SUPPORTED_VERSION_3);
+        }
+
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            if (TAG_MIRRORING_DEVICE.equals(parser.getName())) {
+                parseMirroringDevice(parser);
+            }
+            skip(parser);
+        }
+    }
+
+    private void parseMirroringDevice(XmlPullParser parser) {
+        String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
+        validateOutputDeviceExist(address);
+        CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
+        if (mMirroringDevices.contains(info)) {
+            throw new IllegalArgumentException(TAG_MIRRORING_DEVICE + " " + address
+                    + " repeats, " + TAG_MIRRORING_DEVICES + " can not repeat.");
+        }
+        mMirroringDevices.add(info);
+    }
+
     private void loadCarAudioContexts() {
         if (isVersionLessThanThree() || mCarAudioContextInfos.isEmpty()) {
             mCarAudioContextInfos.addAll(CarAudioContext.getAllContextsInfo());
@@ -710,15 +746,7 @@
         return mCarAudioContext;
     }
 
-    public CarAudioDeviceInfo getMirrorDeviceInfo() {
-        // TODO(b/261647905): Get mirror from car audio configuration.
-        Set<String> addresses = mAddressToCarAudioDeviceInfo.keySet();
-        for (String address : addresses) {
-            if (address.contains("mirror")) {
-                return mAddressToCarAudioDeviceInfo.get(address);
-            }
-        }
-
-        return null;
+    public List<CarAudioDeviceInfo> getMirrorDeviceInfos() {
+        return mMirroringDevices;
     }
 }
diff --git a/tests/carservice_test/res/raw/car_audio_configuration.xml b/tests/carservice_test/res/raw/car_audio_configuration.xml
index 3783cca..edcfcde 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration.xml
@@ -1,5 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
+    <mirroringDevices>
+        <mirroringDevice address="mirror_bus_device_1"/>
+        <mirroringDevice address="mirror_bus_device_2"/>
+    </mirroringDevices>
     <zones>
         <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
             <zoneConfigs>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_mirror_devices.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_mirror_devices.xml
new file mode 100644
index 0000000..8a7baba
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_mirror_devices.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<carAudioConfiguration version="3">
+    <mirroringDevices>
+        <mirroringDevice address="mirror_bus_device_1"/>
+        <mirroringDevice address="mirror_bus_device_2"/>
+        <mirroringDevice address="mirror_bus_device_2"/>
+    </mirroringDevices>
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <zoneConfigs>
+                <zoneConfig  name="primary zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus0_media_out">
+                                <context context="music"/>
+                            </device>
+                            <device address="bus3_call_ring_out">
+                                <context context="call_ring"/>
+                            </device>
+                        </group>
+                        <group>
+                            <device address="bus1_navigation_out">
+                                <context context="navigation"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <zoneConfigs>
+                <zoneConfig name="rear seat zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus100_rear_seat">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+                <zoneConfig name="rear seat zone config 2">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus101_rear_seat">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
index cea7a87..08b461d 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <zones>
         <zone name="primary zone" isPrimary="true">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
index ff13efb..ed9dcbe 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <zones>
         <zone name="primary zone" isPrimary="true">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume.xml
index 4a0c6c1..5396faa 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <oemContexts>
         <oemContext name="OEM_CONTEXT">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_empty_group_name.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_empty_group_name.xml
index 9592410..de0675a 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_empty_group_name.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_empty_group_name.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <oemContexts>
         <oemContext name="OEM_CONTEXT">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_order.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_order.xml
index 2a9a45c..b712851 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_order.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_order.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <zones>
         <zone isPrimary="true" name="primary zone" audioZoneId="0" occupantZoneId="0">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy.xml
index 8fd2606..df94d7d 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <oemContexts>
         <oemContext name="OEM_CONTEXT">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy_2.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy_2.xml
index fb14a59..75db8e3 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy_2.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_strategy_2.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <oemContexts>
         <oemContext name="OEM_CONTEXT">
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_mirroring_V2.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_mirroring_V2.xml
new file mode 100644
index 0000000..77d9a54
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_mirroring_V2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<carAudioConfiguration version="2">
+    <mirroringDevices>
+        <mirroringDevice address="mirror_bus_device"/>
+    </mirroringDevices>
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <volumeGroups>
+                <group>
+                    <device address="bus0_media_out">
+                        <context context="music"/>
+                    </device>
+                    <device address="bus3_call_ring_out">
+                        <context context="call_ring"/>
+                    </device>
+                </group>
+                <group>
+                    <device address="bus1_navigation_out">
+                        <context context="navigation"/>
+                        <context context="emergency"/>
+                        <context context="safety"/>
+                        <context context="vehicle_status"/>
+                        <context context="announcement"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <volumeGroups>
+                <group>
+                    <device address="bus100_rear_seat">
+                        <context context="music"/>
+                        <context context="navigation"/>
+                        <context context="voice_command"/>
+                        <context context="call_ring"/>
+                        <context context="call"/>
+                        <context context="alarm"/>
+                        <context context="system_sound"/>
+                        <context context="notification"/>
+                        <context context="emergency"/>
+                        <context context="safety"/>
+                        <context context="vehicle_status"/>
+                        <context context="announcement"/>
+                    </device>
+                </group>
+            </volumeGroups>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_without_mirroring.xml b/tests/carservice_test/res/raw/car_audio_configuration_without_mirroring.xml
new file mode 100644
index 0000000..2088ac1
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_without_mirroring.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<carAudioConfiguration version="3">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <zoneConfigs>
+                <zoneConfig  name="primary zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus0_media_out">
+                                <context context="music"/>
+                            </device>
+                            <device address="bus3_call_ring_out">
+                                <context context="call_ring"/>
+                            </device>
+                        </group>
+                        <group>
+                            <device address="bus1_navigation_out">
+                                <context context="navigation"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+        <zone name="rear seat zone" audioZoneId="2">
+            <zoneConfigs>
+                <zoneConfig name="rear seat zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus100_rear_seat">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+                <zoneConfig name="rear seat zone config 2">
+                    <volumeGroups>
+                        <group>
+                            <device address="bus101_rear_seat">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
index 83f94b2..a1c0e6c 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
@@ -182,7 +182,7 @@
                 /* canBeRoutedWithDynamicPolicyMix= */ true);
 
         CarAudioDynamicRouting.setupAudioDynamicRoutingForMirrorDevice(mockBuilder,
-                carMirrorDeviceInfo);
+                List.of(carMirrorDeviceInfo));
 
         ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
 
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index 4aa5c10..09dd1d6 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -82,14 +82,8 @@
     public static final int TEST_ANNOUNCEMENT_CONTEXT_ID = TEST_CAR_AUDIO_CONTEXT
             .getContextForAudioAttribute(CarAudioContext
                     .getAudioAttributeFromUsage(USAGE_ANNOUNCEMENT));
-    private List<CarAudioDeviceInfo> mCarAudioOutputDeviceInfos;
-    private AudioDeviceInfo[] mInputAudioDeviceInfos;
-    private InputStream mInputStream;
-    private Context mContext;
-    private CarAudioSettings mCarAudioSettings;
-    @Mock
-    private AudioManager mAudioManager;
-
+    private static final String BUS_MIRROR_DEVICE_1 = "mirror_bus_device_1";
+    private static final String BUS_MIRROR_DEVICE_2 = "mirror_bus_device_2";
     private static final String PRIMARY_ZONE_NAME = "primary zone";
     private static final String PRIMARY_ZONE_CONFIG_NAME = "primary zone config 1";
     private static final String REAR_ZONE_CONFIG_NAME_1 = "rear seat zone config 1";
@@ -109,6 +103,18 @@
 
     private static final int PRIMARY_OCCUPANT_ID = 1;
     private static final int SECONDARY_ZONE_ID = 2;
+    private List<CarAudioDeviceInfo> mCarAudioOutputDeviceInfos;
+    private AudioDeviceInfo[] mInputAudioDeviceInfos;
+    private InputStream mInputStream;
+    private Context mContext;
+    private CarAudioSettings mCarAudioSettings;
+    @Mock
+    private AudioManager mAudioManager;
+
+    @Mock
+    private CarAudioDeviceInfo mTestCarMirrorDeviceOne;
+    @Mock
+    private CarAudioDeviceInfo mTestCarMirrorDeviceTwo;
 
     public CarAudioZonesHelperTest() {
         super(TAG);
@@ -138,6 +144,8 @@
     }
 
     private List<CarAudioDeviceInfo> generateCarDeviceInfos() {
+        mTestCarMirrorDeviceOne = generateCarAudioDeviceInfo(BUS_MIRROR_DEVICE_1);
+        mTestCarMirrorDeviceTwo = generateCarAudioDeviceInfo(BUS_MIRROR_DEVICE_2);
         return ImmutableList.of(
                 generateCarAudioDeviceInfo(BUS_0_ADDRESS),
                 generateCarAudioDeviceInfo(BUS_1_ADDRESS),
@@ -147,7 +155,9 @@
                 generateCarAudioDeviceInfo(""),
                 generateCarAudioDeviceInfo(""),
                 generateCarAudioDeviceInfo(null),
-                generateCarAudioDeviceInfo(null)
+                generateCarAudioDeviceInfo(null),
+                mTestCarMirrorDeviceOne,
+                mTestCarMirrorDeviceTwo
         );
     }
 
@@ -1085,6 +1095,70 @@
         }
     }
 
+    @Test
+    public void getMirrorDeviceInfos() throws Exception {
+        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+                mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
+                /* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
+                /* useCoreAudioRouting= */ false);
+        cazh.loadAudioZones();
+
+        expectWithMessage("Mirror devices").that(cazh.getMirrorDeviceInfos())
+                .containsExactly(mTestCarMirrorDeviceOne, mTestCarMirrorDeviceTwo);
+    }
+
+    @Test
+    public void getMirrorDeviceInfos_withOutMirroringDevices() throws Exception {
+        try (InputStream versionOneStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_without_mirroring)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+                    versionOneStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
+                    /* useCarVolumeGroupMute= */ false,
+                    /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false);
+            cazh.loadAudioZones();
+
+            expectWithMessage("Mirror devices for empty configuration")
+                    .that(cazh.getMirrorDeviceInfos()).isEmpty();
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnMirroringDevicesInV2() throws Exception {
+        try (InputStream versionOneStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_with_mirroring_V2)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+                    versionOneStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
+                    /* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
+                    /* useCoreAudioRouting= */ false);
+
+            IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                    () -> cazh.loadAudioZones());
+
+            expectWithMessage("Mirror devices in v2 configuration exception")
+                    .that(thrown).hasMessageThat().contains("mirroringDevices");
+        }
+    }
+
+    @Test
+    public void loadAudioZones_failsOnDuplicateMirroringDevices() throws Exception {
+        try (InputStream versionOneStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_duplicate_mirror_devices)) {
+            CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+                    versionOneStream,
+                    mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
+                    /* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
+                    /* useCoreAudioRouting= */ false);
+
+            IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                    () -> cazh.loadAudioZones());
+
+            expectWithMessage("Duplicates mirror devices in configuration exception")
+                    .that(thrown).hasMessageThat().contains("can not repeat");
+        }
+    }
+
     private void setupAudioManagerMock() {
         doReturn(CoreAudioRoutingUtils.getProductStrategies())
                 .when(() -> AudioManager.getAudioProductStrategies());
diff --git a/tests/carservice_unit_test/res/raw/car_audio_configuration.xml b/tests/carservice_unit_test/res/raw/car_audio_configuration.xml
index 517cf60..ce70120 100644
--- a/tests/carservice_unit_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_unit_test/res/raw/car_audio_configuration.xml
@@ -13,8 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <carAudioConfiguration version="3">
+    <mirroringDevices>
+        <mirroringDevice address="mirror_bus_device"/>
+    </mirroringDevices>
     <zones>
         <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
             <zoneConfigs>
diff --git a/tests/carservice_unit_test/res/raw/car_audio_configuration_using_core_audio_routing_and_volume.xml b/tests/carservice_unit_test/res/raw/car_audio_configuration_using_core_audio_routing_and_volume.xml
index a83e8cb..3f93611 100644
--- a/tests/carservice_unit_test/res/raw/car_audio_configuration_using_core_audio_routing_and_volume.xml
+++ b/tests/carservice_unit_test/res/raw/car_audio_configuration_using_core_audio_routing_and_volume.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" ?>
+<!-- Copyright (C) 2023 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.
+-->
 <carAudioConfiguration version="3">
     <oemContexts>
         <oemContext name="OEM_CONTEXT">
diff --git a/tests/carservice_unit_test/res/raw/car_audio_configuration_without_mirroring.xml b/tests/carservice_unit_test/res/raw/car_audio_configuration_without_mirroring.xml
new file mode 100644
index 0000000..d153cfa
--- /dev/null
+++ b/tests/carservice_unit_test/res/raw/car_audio_configuration_without_mirroring.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<carAudioConfiguration version="3">
+    <zones>
+        <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+            <zoneConfigs>
+                <zoneConfig  name="primary zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="media_bus_device">
+                                <context context="music"/>
+                                <context context="announcement"/>
+                            </device>
+                            <device address="notification_bus_device">
+                                <context context="notification"/>
+                            </device>
+                        </group>
+                        <group>
+                            <device address="navigation_bus_device">
+                                <context context="navigation"/>
+                            </device>
+                            <device address="voice_bus_device">
+                                <context context="voice_command"/>
+                            </device>
+                        </group>
+                        <group>
+                            <device address="call_bus_device">
+                                <context context="call"/>
+                            </device>
+                            <device address="ring_bus_device">
+                                <context context="call_ring"/>
+                            </device>
+                        </group>
+                        <group>
+                            <device address="alarm_bus_device">
+                                <context context="alarm"/>
+                            </device>
+                            <device address="system_bus_device">
+                                <context context="system_sound"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+        <zone name="secondary zone" audioZoneId="1" occupantZoneId="2">
+            <zoneConfigs>
+                <zoneConfig  name="secondary zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="secondary_zone_bus_1">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+                <zoneConfig  name="secondary zone config 2">
+                    <volumeGroups>
+                        <group>
+                            <device address="secondary_zone_bus_2">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+        <zone name="tertiary zone" audioZoneId="2" occupantZoneId="3">
+            <zoneConfigs>
+                <zoneConfig  name="tertiary zone config 1" isDefault="true">
+                    <volumeGroups>
+                        <group>
+                            <device address="tertiary_zone_bus_1">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+                <zoneConfig  name="tertiary zone config 2">
+                    <volumeGroups>
+                        <group>
+                            <device address="tertiary_zone_bus_2">
+                                <context context="music"/>
+                                <context context="navigation"/>
+                                <context context="voice_command"/>
+                                <context context="call_ring"/>
+                                <context context="call"/>
+                                <context context="alarm"/>
+                                <context context="system_sound"/>
+                                <context context="notification"/>
+                                <context context="emergency"/>
+                                <context context="safety"/>
+                                <context context="vehicle_status"/>
+                                <context context="announcement"/>
+                            </device>
+                        </group>
+                    </volumeGroups>
+                </zoneConfig>
+            </zoneConfigs>
+        </zone>
+    </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioMirrorRequestHandlerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioMirrorRequestHandlerTest.java
index 63d0332..63f2070 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioMirrorRequestHandlerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioMirrorRequestHandlerTest.java
@@ -20,35 +20,50 @@
 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
 
 import android.car.media.CarAudioManager;
 import android.car.media.IAudioZonesMirrorStatusCallback;
 import android.car.test.AbstractExpectableTestCase;
+import android.media.AudioDeviceInfo;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(MockitoJUnitRunner.class)
 public final class CarAudioMirrorRequestHandlerTest extends AbstractExpectableTestCase {
 
-    private static final String TEST_MIRROR_DEVICE_ADDRESS = "bus_666_mirror_device";
     private static final int TEST_ZONE_1 = PRIMARY_AUDIO_ZONE + 1;
     private static final int TEST_ZONE_2 = PRIMARY_AUDIO_ZONE + 2;
     private static final int TEST_ZONE_3 = PRIMARY_AUDIO_ZONE + 3;
     public static final int[] TEST_ZONE_IDS = new int[]{TEST_ZONE_1, TEST_ZONE_2};
     private CarAudioMirrorRequestHandler mCarAudioMirrorRequestHandler;
     private TestAudioZonesMirrorStatusCallbackCallback mTestCallback;
+    @Mock
+    private CarAudioDeviceInfo mCarAudioDeviceInfoOne;
+    @Mock
+    private AudioDeviceInfo mAudioDeviceInfoOne;
+    @Mock
+    private CarAudioDeviceInfo mCarAudioDeviceInfoTwo;
+    @Mock
+    private AudioDeviceInfo mAudioDeviceInfoTwo;
+    private List<CarAudioDeviceInfo> mTestCarAudioDeviceInfos;
 
     @Before
     public void setUp() {
         mCarAudioMirrorRequestHandler = new CarAudioMirrorRequestHandler();
-        mCarAudioMirrorRequestHandler.setMirrorDeviceAddress(TEST_MIRROR_DEVICE_ADDRESS);
         mTestCallback = new TestAudioZonesMirrorStatusCallbackCallback();
+        mTestCarAudioDeviceInfos = List.of(mCarAudioDeviceInfoOne, mCarAudioDeviceInfoTwo);
+        mCarAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos);
+        when(mCarAudioDeviceInfoOne.getAudioDeviceInfo()).thenReturn(mAudioDeviceInfoOne);
+        when(mCarAudioDeviceInfoTwo.getAudioDeviceInfo()).thenReturn(mAudioDeviceInfoTwo);
     }
 
     @Test
@@ -158,30 +173,72 @@
     }
 
     @Test
-    public void setMirrorDeviceAddress_withNullMirrorDeviceAddress() {
+    public void setMirrorDeviceInfos_withNullMirrorDeviceAddress() {
         CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
                 new CarAudioMirrorRequestHandler();
 
         NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
-            carAudioMirrorRequestHandler.setMirrorDeviceAddress(/* deviceAddress= */ null);
+            carAudioMirrorRequestHandler.setMirrorDeviceInfos(/* deviceAddress= */ null);
         });
 
-        expectWithMessage("Null mirror device address exception")
-                .that(thrown).hasMessageThat().contains("Mirror device address");
+        expectWithMessage("Null mirror device infos exception")
+                .that(thrown).hasMessageThat().contains("Mirror devices");
     }
 
     @Test
-    public void setMirrorDeviceAddress() {
+    public void setMirrorDeviceInfos() {
         CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
                 new CarAudioMirrorRequestHandler();
 
-        carAudioMirrorRequestHandler.setMirrorDeviceAddress(TEST_MIRROR_DEVICE_ADDRESS);
+        carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos);
 
         expectWithMessage("Audio mirror enabled status")
                 .that(carAudioMirrorRequestHandler.isMirrorAudioEnabled()).isTrue();
     }
 
     @Test
+    public void getMirroringDeviceInfos() {
+        CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
+                new CarAudioMirrorRequestHandler();
+        carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos);
+
+        expectWithMessage("Audio mirror device infos")
+                .that(carAudioMirrorRequestHandler.getMirroringDeviceInfos())
+                .containsExactlyElementsIn(mTestCarAudioDeviceInfos);
+    }
+
+    @Test
+    public void getMirroringDeviceInfos_withEmptyDeviceInfos() {
+        CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
+                new CarAudioMirrorRequestHandler();
+        carAudioMirrorRequestHandler.setMirrorDeviceInfos(List.of());
+
+        expectWithMessage("Empty audio mirror device infos")
+                .that(carAudioMirrorRequestHandler.getMirroringDeviceInfos()).isEmpty();
+    }
+
+    @Test
+    public void getAudioDeviceInfo() {
+        CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
+                new CarAudioMirrorRequestHandler();
+        carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos);
+
+        expectWithMessage("Audio mirror device info")
+                .that(carAudioMirrorRequestHandler.getAudioDeviceInfo())
+                .isEqualTo(mAudioDeviceInfoOne);
+    }
+
+    @Test
+    public void getAudioDeviceInfo_withEmptyDeviceInfos() {
+        CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
+                new CarAudioMirrorRequestHandler();
+        carAudioMirrorRequestHandler.setMirrorDeviceInfos(List.of());
+
+        expectWithMessage("Null audio mirror device info")
+                .that(carAudioMirrorRequestHandler.getAudioDeviceInfo()).isNull();
+    }
+
+    @Test
     public void isMirrorAudioEnabled_withoutMirrorDeviceAvailable() {
         CarAudioMirrorRequestHandler carAudioMirrorRequestHandler =
                 new CarAudioMirrorRequestHandler();
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
index bdaf509..720cff8 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
@@ -379,6 +379,7 @@
     private TemporaryFile mTemporaryAudioConfigurationUsingCoreAudioFile;
     private TemporaryFile mTemporaryAudioConfigurationFile;
     private TemporaryFile mTemporaryAudioConfigurationWithoutZoneMappingFile;
+    private TemporaryFile mTemporaryAudioConfigurationWithoutMirroringFile;
     private Context mContext;
     private AudioDeviceInfo mMicrophoneInputDevice;
     private AudioDeviceInfo mFmTunerInputDevice;
@@ -433,6 +434,15 @@
                     + mTemporaryAudioConfigurationUsingCoreAudioFile.getPath());
         }
 
+        try (InputStream configurationStream = mContext.getResources().openRawResource(
+                R.raw.car_audio_configuration_without_mirroring)) {
+            mTemporaryAudioConfigurationWithoutMirroringFile = new TemporaryFile("xml");
+            mTemporaryAudioConfigurationWithoutMirroringFile
+                    .write(new String(configurationStream.readAllBytes()));
+            Log.i(TAG, "Temporary Car Audio Configuration Without Mirroring File Location: "
+                    + mTemporaryAudioConfigurationWithoutMirroringFile.getPath());
+        }
+
         mockCoreAudioRoutingAndVolume();
         mockGrantCarControlAudioSettingsPermission();
 
@@ -3872,11 +3882,11 @@
     }
 
     private CarAudioService getCarAudioServiceWithoutMirroring() {
-        AudioDeviceInfo[] outputDevices = getOutputDevicesWithNoMirrorDevice();
+        AudioDeviceInfo[] outputDevices = generateOutputDeviceInfos();
         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(outputDevices);
         CarAudioService carAudioService =
-                new CarAudioService(mMockContext, mTemporaryAudioConfigurationFile.getFile()
-                        .getAbsolutePath(), mCarVolumeCallbackHandler);
+                new CarAudioService(mMockContext, mTemporaryAudioConfigurationWithoutMirroringFile
+                        .getFile().getAbsolutePath(), mCarVolumeCallbackHandler);
         carAudioService.init();
         return carAudioService;
     }
@@ -3923,68 +3933,6 @@
         return new AudioDeviceInfo[]{mMicrophoneInputDevice, mFmTunerInputDevice};
     }
 
-    private AudioDeviceInfo[] getOutputDevicesWithNoMirrorDevice() {
-        return new AudioDeviceInfo[] {
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(MEDIA_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(NAVIGATION_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(CALL_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(SYSTEM_BUS_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(NOTIFICATION_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(VOICE_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(RING_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(ALARM_TEST_DEVICE)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(SECONDARY_TEST_DEVICE_1)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(SECONDARY_TEST_DEVICE_2)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(TERTIARY_TEST_DEVICE_1)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(TERTIARY_TEST_DEVICE_2)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(QUATERNARY_TEST_DEVICE_1)
-                        .build(),
-                new AudioDeviceInfoBuilder()
-                        .setAudioGains(new AudioGain[] {new GainBuilder().build()})
-                        .setAddressName(OEM_TEST_DEVICE)
-                        .build(),
-
-        };
-    }
-
     private AudioDeviceInfo[] generateOutputDeviceInfos() {
         mMediaOutputDevice = new AudioDeviceInfoBuilder()
                 .setAudioGains(new AudioGain[] {new GainBuilder().build()})