Use SparseArray for carAudioZones

To support any integer value for an audio zoneId, CarAudioZone[] is
being replaced with SparseArray<CarAudioZone>.

Fixes: 174171712
Test: atest com.android.car.audio
Change-Id: Id2930304f06ca8ef04bfb82c86e31fa00efe8ed7
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
index ea61542..6b49bf8 100644
--- a/service/src/com/android/car/audio/CarAudioDynamicRouting.java
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -23,6 +23,7 @@
 import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicy;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.car.CarLog;
 
@@ -31,7 +32,7 @@
 /**
  * Builds dynamic audio routing in a car from audio zone configuration.
  */
-/* package */ class CarAudioDynamicRouting {
+final class CarAudioDynamicRouting {
     // For legacy stream type based volume control.
     // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
     static final int[] STREAM_TYPES = new int[] {
@@ -45,14 +46,10 @@
             AudioAttributes.USAGE_NOTIFICATION_RINGTONE
     };
 
-    private final CarAudioZone[] mCarAudioZones;
-
-    CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
-        mCarAudioZones = carAudioZones;
-    }
-
-    void setupAudioDynamicRouting(AudioPolicy.Builder builder) {
-        for (CarAudioZone zone : mCarAudioZones) {
+    static void setupAudioDynamicRouting(AudioPolicy.Builder builder,
+            SparseArray<CarAudioZone> carAudioZones) {
+        for (int i = 0; i < carAudioZones.size(); i++) {
+            CarAudioZone zone = carAudioZones.valueAt(i);
             for (CarVolumeGroup group : zone.getVolumeGroups()) {
                 setupAudioDynamicRoutingForGroup(group, builder);
             }
@@ -64,7 +61,7 @@
      * @param group {@link CarVolumeGroup} instance to enumerate the buses with
      * @param builder {@link AudioPolicy.Builder} to attach the mixing rules
      */
-    private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
+    private static void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
             AudioPolicy.Builder builder) {
         // Note that one can not register audio mix for same bus more than once.
         for (String address : group.getAddresses()) {
@@ -105,7 +102,7 @@
         }
     }
 
-    private AudioAttributes buildAttributesWithUsage(@AttributeUsage int usage) {
+    private static AudioAttributes buildAttributesWithUsage(@AttributeUsage int usage) {
         AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
         if (AudioAttributes.isSystemUsage(usage)) {
             attributesBuilder.setSystemUsage(usage);
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index bc67085..fe373d9 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -54,6 +54,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 
@@ -67,6 +68,7 @@
 import com.android.car.audio.hal.AudioControlWrapper;
 import com.android.car.audio.hal.AudioControlWrapperV1;
 import com.android.car.audio.hal.HalAudioFocus;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -212,7 +214,8 @@
     private CarZonesAudioFocus mFocusHandler;
     private String mCarAudioConfigurationPath;
     private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
-    private CarAudioZone[] mCarAudioZones;
+    @GuardedBy("mImplLock")
+    private SparseArray<CarAudioZone> mCarAudioZones;
     private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
     private final SparseIntArray mAudioZoneIdToUserIdMapping;
 
@@ -303,8 +306,11 @@
         // Empty line for comfortable reading
         writer.println();
         if (mUseDynamicRouting) {
-            for (CarAudioZone zone : mCarAudioZones) {
-                zone.dump("\t", writer);
+            synchronized (mImplLock) {
+                for (int i = 0; i < mCarAudioZones.size(); i++) {
+                    CarAudioZone zone = mCarAudioZones.valueAt(i);
+                    zone.dump("\t", writer);
+                }
             }
             writer.println();
             writer.println("\tUserId to Zone Mapping:");
@@ -358,7 +364,7 @@
                 return;
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
+            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
             group.setCurrentGainIndex(index);
         }
     }
@@ -400,7 +406,7 @@
                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
+            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
             return group.getMaxGainIndex();
         }
     }
@@ -419,7 +425,7 @@
                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
+            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
             return group.getMinGainIndex();
         }
     }
@@ -438,16 +444,14 @@
                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
+            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
             return group.getCurrentGainIndex();
         }
     }
 
-    private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
-        Objects.requireNonNull(mCarAudioZones);
-        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
-                "zoneId out of range: " + zoneId);
-        return mCarAudioZones[zoneId].getVolumeGroup(groupId);
+    @GuardedBy("mImplLock")
+    private CarVolumeGroup getCarVolumeGroupLocked(int zoneId, int groupId) {
+        return getCarAudioZoneLocked(zoneId).getVolumeGroup(groupId);
     }
 
     private void setupLegacyVolumeChangedListener() {
@@ -472,7 +476,7 @@
                 AudioManager.GET_DEVICES_INPUTS);
     }
 
-    private CarAudioZone[] loadCarAudioConfigurationLocked(
+    private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
         AudioDeviceInfo[] inputDevices = getAllInputDevices();
         try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
@@ -486,7 +490,7 @@
         }
     }
 
-    private CarAudioZone[] loadVolumeGroupConfigurationWithAudioControlLocked(
+    private SparseArray<CarAudioZone> loadVolumeGroupConfigurationWithAudioControlLocked(
             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
         if (!(audioControlWrapper instanceof AudioControlWrapperV1)) {
@@ -500,6 +504,7 @@
         return legacyHelper.loadAudioZones();
     }
 
+    @GuardedBy("mImplLock")
     private void loadCarAudioZonesLocked() {
         List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
 
@@ -514,21 +519,21 @@
         CarAudioZonesValidator.validate(mCarAudioZones);
     }
 
+    @GuardedBy("mImplLock")
     private void setupDynamicRoutingLocked() {
         final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
         builder.setLooper(Looper.getMainLooper());
 
         loadCarAudioZonesLocked();
 
-        for (CarAudioZone zone : mCarAudioZones) {
+        for (int i = 0; i < mCarAudioZones.size(); i++) {
+            CarAudioZone zone = mCarAudioZones.valueAt(i);
             // Ensure HAL gets our initial value
             zone.synchronizeCurrentGainIndex();
             Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
         }
 
-        // Setup dynamic routing rules by usage
-        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
-        dynamicRouting.setupAudioDynamicRouting(builder);
+        CarAudioDynamicRouting.setupAudioDynamicRouting(builder, mCarAudioZones);
 
         // Attach the {@link AudioPolicyVolumeCallback}
         builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
@@ -662,7 +667,7 @@
 
     @Override
     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
-            @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
+            @AttributeUsage int usage, int gainInMillibels) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
             return createAudioPatchLocked(sourceAddress, usage, gainInMillibels);
@@ -678,7 +683,7 @@
     }
 
     private CarAudioPatchHandle createAudioPatchLocked(String sourceAddress,
-            @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
+            @AttributeUsage int usage, int gainInMillibels) {
         // Find the named source port
         AudioDeviceInfo sourcePortInfo = null;
         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
@@ -772,14 +777,12 @@
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;
 
-            Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
-                    "zoneId out of range: " + zoneId);
-            return mCarAudioZones[zoneId].getVolumeGroupCount();
+            return getCarAudioZoneLocked(zoneId).getVolumeGroupCount();
         }
     }
 
     @Override
-    public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
+    public int getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
 
@@ -793,16 +796,14 @@
                 return INVALID_VOLUME_GROUP_ID;
             }
 
-            Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
-                    "zoneId out of range: " + zoneId);
-
             @AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
             return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
         }
     }
 
+    @GuardedBy("mImplLock")
     private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
-        CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
+        CarVolumeGroup[] groups = getCarAudioZoneLocked(zoneId).getVolumeGroups();
         for (int i = 0; i < groups.length; i++) {
             int[] groupAudioContexts = groups[i].getContexts();
             for (int groupAudioContext : groupAudioContexts) {
@@ -824,13 +825,13 @@
                 return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
+            CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
             Set<Integer> contexts =
                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
             final List<Integer> usages = new ArrayList<>();
             for (@AudioContext int context : contexts) {
                 int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
-                for (@AudioAttributes.AttributeUsage int usage : usagesForContext) {
+                for (@AttributeUsage int usage : usagesForContext) {
                     usages.add(usage);
                 }
             }
@@ -848,7 +849,11 @@
         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
         requireDynamicRouting();
         synchronized (mImplLock) {
-            return Arrays.stream(mCarAudioZones).mapToInt(CarAudioZone::getId).toArray();
+            int[] zoneIds = new int[mCarAudioZones.size()];
+            for (int i = 0; i < mCarAudioZones.size(); i++) {
+                zoneIds[i] = mCarAudioZones.keyAt(i);
+            }
+            return zoneIds;
         }
     }
 
@@ -902,9 +907,8 @@
     public boolean setZoneIdForUid(int zoneId, int uid) {
         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
         requireDynamicRouting();
-        Preconditions.checkArgument(isAudioZoneIdValid(zoneId),
-                "Invalid audio zone id %d", zoneId);
         synchronized (mImplLock) {
+            checkAudioZoneIdLocked(zoneId);
             Log.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
                     + uid + " mapped to : "
                     + zoneId);
@@ -959,16 +963,13 @@
     }
 
     @Override
-    public String getOutputDeviceAddressForUsage(int zoneId,
-            @AudioAttributes.AttributeUsage int usage) {
+    public String getOutputDeviceAddressForUsage(int zoneId, @AttributeUsage int usage) {
         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
         requireDynamicRouting();
-        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
-                "zoneId (" + zoneId + ")");
         int contextForUsage = CarAudioContext.getContextForUsage(usage);
         Preconditions.checkArgument(contextForUsage != CarAudioContext.INVALID,
                 "Invalid audio attribute usage %d", usage);
-        return mCarAudioZones[zoneId].getAddressForContext(contextForUsage);
+        return getCarAudioZone(zoneId).getAddressForContext(contextForUsage);
     }
 
     /**
@@ -1018,11 +1019,13 @@
      * @param uid uid to map
      * @return true if setting uid device affinity is successful
      */
+    @GuardedBy("mImplLock")
     private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
         Log.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
                 + uid + " mapped to " + zoneId);
         //Request to add uid device affinity
-        if (mAudioPolicy.setUidDeviceAffinity(uid, mCarAudioZones[zoneId].getAudioDeviceInfos())) {
+        List<AudioDeviceInfo> deviceInfos = getCarAudioZoneLocked(zoneId).getAudioDeviceInfos();
+        if (mAudioPolicy.setUidDeviceAffinity(uid, deviceInfos)) {
             // TODO do not store uid mapping here instead use the uid
             //  device affinity in audio policy when available
             mUidToZoneMap.put(uid, zoneId);
@@ -1077,14 +1080,8 @@
     public @NonNull List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
         requireDynamicRouting();
-        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
-                "zoneId out of range: " + zoneId);
-        for (CarAudioZone zone : mCarAudioZones) {
-            if (zone.getId() == zoneId) {
-                return zone.getInputAudioDevices();
-            }
-        }
-        throw new IllegalArgumentException("zoneId does not exist" + zoneId);
+
+        return getCarAudioZone(zoneId).getInputAudioDevices();
     }
 
     private void enforcePermission(String permissionName) {
@@ -1109,11 +1106,11 @@
      * @return {@link AudioDevicePort} that handles the given car audio usage.
      * Multiple usages may share one {@link AudioDevicePort}
      */
-    private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
+    private @Nullable AudioDevicePort getAudioPort(@AttributeUsage int usage) {
         int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
         final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
         final CarVolumeGroup group = Objects.requireNonNull(
-                mCarAudioZones[zoneId].getVolumeGroup(groupId),
+                getCarVolumeGroupLocked(zoneId, groupId),
                 "Can not find CarVolumeGroup by usage: "
                         + AudioAttributes.usageToString(usage));
         return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
@@ -1147,8 +1144,8 @@
         synchronized (mImplLock) {
             if (!isOccupantZoneMappingAvailableLocked()) {
                 //No occupant zone to audio zone mapping, re-adjust to settings driver.
-                for (int index = 0; index < mCarAudioZones.length; index++) {
-                    CarAudioZone zone = mCarAudioZones[index];
+                for (int i = 0; i < mCarAudioZones.size(); i++) {
+                    CarAudioZone zone = mCarAudioZones.valueAt(i);
                     zone.updateVolumeGroupsForUser(driverUserId);
                     mFocusHandler.updateUserForZoneId(zone.getId(), driverUserId);
                 }
@@ -1168,16 +1165,13 @@
         return mAudioZoneIdToOccupantZoneIdMapping.size() > 0;
     }
 
+    @GuardedBy("mImplLock")
     private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId,
             @UserIdInt int driverUserId, int occupantZoneForDriver) {
-        CarAudioZone zone = getAudioZoneForZoneIdLocked(audioZoneId);
+        CarAudioZone audioZone = getCarAudioZoneLocked(audioZoneId);
         int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
         int prevUserId = getUserIdForZoneLocked(audioZoneId);
 
-        Objects.requireNonNull(zone, () ->
-                "setUserIdDeviceAffinity for userId " + userId
-                        + " in zone " + audioZoneId + " Failed, invalid zone.");
-
         // user in occupant zone has not changed
         if (userId == prevUserId) {
             return;
@@ -1189,16 +1183,16 @@
 
         if (userId == UserHandle.USER_NULL) {
             // Reset zone back to driver user id
-            resetZoneToDefaultUser(zone, driverUserId);
+            resetZoneToDefaultUser(audioZone, driverUserId);
             return;
         }
 
         // Only set user id device affinities for driver when it is the driver's occupant zone
         if (userId != driverUserId || occupantZoneId == occupantZoneForDriver) {
-            setUserIdDeviceAffinitiesLocked(zone, userId, audioZoneId);
+            setUserIdDeviceAffinitiesLocked(audioZone, userId, audioZoneId);
             mAudioZoneIdToUserIdMapping.put(audioZoneId, userId);
         }
-        zone.updateVolumeGroupsForUser(userId);
+        audioZone.updateVolumeGroupsForUser(userId);
         mFocusHandler.updateUserForZoneId(audioZoneId, userId);
     }
 
@@ -1232,15 +1226,6 @@
         mFocusHandler.updateUserForZoneId(audioZoneId, driverUserId);
     }
 
-    private CarAudioZone getAudioZoneForZoneIdLocked(int audioZoneId) {
-        for (CarAudioZone zone : mCarAudioZones) {
-            if (zone.getId() == audioZoneId) {
-                return zone;
-            }
-        }
-        return null;
-    }
-
     private void removeUserIdDeviceAffinitiesLocked(@UserIdInt int userId) {
         if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
             Log.d(CarLog.TAG_AUDIO,
@@ -1275,12 +1260,27 @@
     }
 
     boolean isAudioZoneIdValid(int zoneId) {
-        for (CarAudioZone zone : mCarAudioZones) {
-            if (zone.getId() == zoneId) {
-                return true;
-            }
+        synchronized (mImplLock) {
+            return mCarAudioZones.contains(zoneId);
         }
-        return false;
+    }
+
+    private CarAudioZone getCarAudioZone(int zoneId) {
+        synchronized (mImplLock) {
+            return getCarAudioZoneLocked(zoneId);
+        }
+    }
+
+    @GuardedBy("mImplLock")
+    private CarAudioZone getCarAudioZoneLocked(int zoneId) {
+        checkAudioZoneIdLocked(zoneId);
+        return mCarAudioZones.get(zoneId);
+    }
+
+    @GuardedBy("mImplLock")
+    private void checkAudioZoneIdLocked(int zoneId) {
+        Preconditions.checkArgument(mCarAudioZones.contains(zoneId),
+                "Invalid audio zone Id " + zoneId);
     }
 
     private class CarAudioOccupantConfigChangeListener implements OccupantZoneConfigChangeListener {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 1a4e0ad..810aca3 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -15,11 +15,13 @@
  */
 package com.android.car.audio;
 
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+
 import android.annotation.NonNull;
-import android.car.media.CarAudioManager;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.text.TextUtils;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.Xml;
 
@@ -33,7 +35,6 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -132,7 +133,6 @@
     private final Set<Integer> mAudioZoneIds;
     private final Set<String> mInputAudioDevices;
 
-    private boolean mHasPrimaryZone;
     private int mNextSecondaryZoneId;
     private int mCurrentVersion;
 
@@ -152,7 +152,7 @@
                 carAudioDeviceInfos);
         mAddressToInputAudioDeviceInfo =
                 CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
-        mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
+        mNextSecondaryZoneId = PRIMARY_AUDIO_ZONE + 1;
         mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
         mAudioZoneIds = new HashSet<>();
         mInputAudioDevices = new HashSet<>();
@@ -162,11 +162,8 @@
         return mZoneIdToOccupantZoneIdMapping;
     }
 
-    // TODO: refactor this method to return List<CarAudioZone>
-    CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
-        List<CarAudioZone> carAudioZones = new ArrayList<>();
-        parseCarAudioZones(carAudioZones, mInputStream);
-        return carAudioZones.toArray(new CarAudioZone[0]);
+    SparseArray<CarAudioZone> loadAudioZones() throws IOException, XmlPullParserException {
+        return parseCarAudioZones(mInputStream);
     }
 
     private static Map<String, CarAudioDeviceInfo> generateAddressToInfoMap(
@@ -189,9 +186,9 @@
         return deviceAddressToInputDeviceMap;
     }
 
-    private void parseCarAudioZones(List<CarAudioZone> carAudioZones, InputStream stream)
+    private SparseArray<CarAudioZone> parseCarAudioZones(InputStream stream)
             throws XmlPullParserException, IOException {
-        final XmlPullParser parser = Xml.newPullParser();
+        XmlPullParser parser = Xml.newPullParser();
         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);
         parser.setInput(stream, null);
 
@@ -214,35 +211,50 @@
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_AUDIO_ZONES.equals(parser.getName())) {
-                parseAudioZones(parser, carAudioZones);
+                return parseAudioZones(parser);
             } else {
                 skip(parser);
             }
         }
+        throw new RuntimeException(TAG_AUDIO_ZONES + " is missing from configuration");
     }
 
-    private void parseAudioZones(XmlPullParser parser, List<CarAudioZone> carAudioZones)
+    private SparseArray<CarAudioZone> parseAudioZones(XmlPullParser parser)
             throws XmlPullParserException, IOException {
+        SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
+
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_AUDIO_ZONE.equals(parser.getName())) {
-                carAudioZones.add(parseAudioZone(parser));
+                CarAudioZone zone = parseAudioZone(parser);
+                verifyOnlyOnePrimaryZone(zone, carAudioZones);
+                carAudioZones.put(zone.getId(), zone);
             } else {
                 skip(parser);
             }
         }
-        Preconditions.checkArgument(mHasPrimaryZone, "Requires one primary zone");
-        carAudioZones.sort(Comparator.comparing(CarAudioZone::getId));
+
+        verifyPrimaryZonePresent(carAudioZones);
+        return carAudioZones;
+    }
+
+    private void verifyOnlyOnePrimaryZone(CarAudioZone newZone, SparseArray<CarAudioZone> zones) {
+        if (newZone.getId() == PRIMARY_AUDIO_ZONE && zones.contains(PRIMARY_AUDIO_ZONE)) {
+            throw new RuntimeException("More than one zone parsed with primary audio zone ID: "
+                            + PRIMARY_AUDIO_ZONE);
+        }
+    }
+
+    private void verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones) {
+        if (!zones.contains(PRIMARY_AUDIO_ZONE)) {
+            throw new RuntimeException("Primary audio zone is required");
+        }
     }
 
     private CarAudioZone parseAudioZone(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         final boolean isPrimary = Boolean.parseBoolean(
                 parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY));
-        if (isPrimary) {
-            Preconditions.checkArgument(!mHasPrimaryZone, "Only one primary zone is allowed");
-            mHasPrimaryZone = true;
-        }
         final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME);
         final int audioZoneId = getZoneId(isPrimary, parser);
         parseOccupantZoneId(audioZoneId, parser);
@@ -268,25 +280,25 @@
                     "Invalid audio attribute %s"
                             + ", Please update car audio configurations file "
                             + "to version to 2 to use it.", ATTR_ZONE_ID);
-            return isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE
+            return isPrimary ? PRIMARY_AUDIO_ZONE
                     : getNextSecondaryZoneId();
         }
         // Primary zone does not need to define it
         if (isPrimary && audioZoneIdString == null) {
-            return CarAudioManager.PRIMARY_AUDIO_ZONE;
+            return PRIMARY_AUDIO_ZONE;
         }
         Objects.requireNonNull(audioZoneIdString, () ->
                 "Requires " + ATTR_ZONE_ID + " for all audio zones.");
         int zoneId = parsePositiveIntAttribute(ATTR_ZONE_ID, audioZoneIdString);
         //Verify that primary zone id is PRIMARY_AUDIO_ZONE
         if (isPrimary) {
-            Preconditions.checkArgument(zoneId == CarAudioManager.PRIMARY_AUDIO_ZONE,
+            Preconditions.checkArgument(zoneId == PRIMARY_AUDIO_ZONE,
                     "Primary zone %s must be %d or it can be left empty.",
-                    ATTR_ZONE_ID, CarAudioManager.PRIMARY_AUDIO_ZONE);
+                    ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE);
         } else {
-            Preconditions.checkArgument(zoneId != CarAudioManager.PRIMARY_AUDIO_ZONE,
+            Preconditions.checkArgument(zoneId != PRIMARY_AUDIO_ZONE,
                     "%s can only be %d for primary zone.",
-                    ATTR_ZONE_ID, CarAudioManager.PRIMARY_AUDIO_ZONE);
+                    ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE);
         }
         validateAudioZoneIdIsUnique(zoneId);
         return zoneId;
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 1b447f3..2b979ad 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -15,11 +15,12 @@
  */
 package com.android.car.audio;
 
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+
 import static com.android.car.audio.CarAudioZonesHelper.LEGACY_CONTEXTS;
 
 import android.annotation.NonNull;
 import android.annotation.XmlRes;
-import android.car.media.CarAudioManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -125,14 +126,16 @@
         return busToCarAudioDeviceInfo;
     }
 
-    CarAudioZone[] loadAudioZones() {
-        final CarAudioZone zone = new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE,
+    SparseArray<CarAudioZone> loadAudioZones() {
+        final CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE,
                 "Primary zone");
         for (CarVolumeGroup group : loadVolumeGroups()) {
             zone.addVolumeGroup(group);
             bindContextsForVolumeGroup(group);
         }
-        return new CarAudioZone[]{zone};
+        SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
+        carAudioZones.put(PRIMARY_AUDIO_ZONE, zone);
+        return carAudioZones;
     }
 
     private void bindContextsForVolumeGroup(CarVolumeGroup group) {
@@ -201,7 +204,7 @@
             }
         }
 
-        return new CarVolumeGroup(mCarAudioSettings, CarAudioManager.PRIMARY_AUDIO_ZONE, id,
+        return new CarVolumeGroup(mCarAudioSettings, PRIMARY_AUDIO_ZONE, id,
                 contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
     }
 
diff --git a/service/src/com/android/car/audio/CarAudioZonesValidator.java b/service/src/com/android/car/audio/CarAudioZonesValidator.java
index 1bb0234..4a74cd8 100644
--- a/service/src/com/android/car/audio/CarAudioZonesValidator.java
+++ b/service/src/com/android/car/audio/CarAudioZonesValidator.java
@@ -16,24 +16,27 @@
 package com.android.car.audio;
 
 
+import android.util.SparseArray;
+
 import java.util.HashSet;
 import java.util.Set;
 
 class CarAudioZonesValidator {
-    static void validate(CarAudioZone[] carAudioZones) {
+    static void validate(SparseArray<CarAudioZone> carAudioZones) {
         validateAtLeastOneZoneDefined(carAudioZones);
         validateVolumeGroupsForEachZone(carAudioZones);
         validateEachAddressAppearsAtMostOnce(carAudioZones);
     }
 
-    private static void validateAtLeastOneZoneDefined(CarAudioZone[] carAudioZones) {
-        if (carAudioZones.length == 0) {
+    private static void validateAtLeastOneZoneDefined(SparseArray<CarAudioZone> carAudioZones) {
+        if (carAudioZones.size() == 0) {
             throw new RuntimeException("At least one zone should be defined");
         }
     }
 
-    private static void validateVolumeGroupsForEachZone(CarAudioZone[] carAudioZones) {
-        for (CarAudioZone zone : carAudioZones) {
+    private static void validateVolumeGroupsForEachZone(SparseArray<CarAudioZone> carAudioZones) {
+        for (int i = 0; i < carAudioZones.size(); i++) {
+            CarAudioZone zone = carAudioZones.valueAt(i);
             if (!zone.validateVolumeGroups()) {
                 throw new RuntimeException(
                         "Invalid volume groups configuration for zone " + zone.getId());
@@ -41,9 +44,11 @@
         }
     }
 
-    private static void validateEachAddressAppearsAtMostOnce(CarAudioZone[] carAudioZones) {
+    private static void validateEachAddressAppearsAtMostOnce(
+            SparseArray<CarAudioZone> carAudioZones) {
         Set<String> addresses = new HashSet<>();
-        for (CarAudioZone zone : carAudioZones) {
+        for (int i = 0; i < carAudioZones.size(); i++) {
+            CarAudioZone zone = carAudioZones.valueAt(i);
             for (CarVolumeGroup carVolumeGroup : zone.getVolumeGroups()) {
                 for (String address : carVolumeGroup.getAddresses()) {
                     if (!addresses.add(address)) {
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index ec024be..775a8a3 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -26,6 +26,7 @@
 import android.media.audiopolicy.AudioPolicy;
 import android.os.Bundle;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.car.CarLog;
 import com.android.internal.util.Preconditions;
@@ -51,7 +52,7 @@
 
     CarZonesAudioFocus(@NonNull AudioManager audioManager,
             @NonNull PackageManager packageManager,
-            @NonNull CarAudioZone[] carAudioZones,
+            @NonNull SparseArray<CarAudioZone> carAudioZones,
             @NonNull CarAudioSettings carAudioSettings,
             boolean enableDelayedAudioFocus) {
         //Create the zones here, the policy will be set setOwningPolicy,
@@ -60,11 +61,12 @@
         Objects.requireNonNull(packageManager);
         Objects.requireNonNull(carAudioZones);
         Objects.requireNonNull(carAudioSettings);
-        Preconditions.checkArgument(carAudioZones.length != 0,
+        Preconditions.checkArgument(carAudioZones.size() != 0,
                 "There must be a minimum of one audio zone");
 
         //Create focus for all the zones
-        for (CarAudioZone audioZone : carAudioZones) {
+        for (int i = 0; i < carAudioZones.size(); i++) {
+            CarAudioZone audioZone = carAudioZones.valueAt(i);
             int audioZoneId = audioZone.getId();
             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
                 Log.d(CarLog.TAG_AUDIO,
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
index 170ae8c..b0e932e 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
@@ -23,6 +23,7 @@
 
 import android.annotation.XmlRes;
 import android.content.Context;
+import android.util.SparseArray;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -90,9 +91,9 @@
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
                 carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
 
-        CarAudioZone[] zones = helper.loadAudioZones();
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
-        assertThat(zones).hasLength(1);
+        assertThat(zones.size()).isEqualTo(1);
     }
 
     @Test
@@ -104,8 +105,8 @@
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
                 carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
 
-        CarAudioZone[] zones = helper.loadAudioZones();
-        CarVolumeGroup[] volumeGroups = zones[0].getVolumeGroups();
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
+        CarVolumeGroup[] volumeGroups = zones.get(0).getVolumeGroups();
         assertThat(volumeGroups).hasLength(2);
     }
 
@@ -119,9 +120,9 @@
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
                 carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
 
-        CarAudioZone[] zones = helper.loadAudioZones();
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
-        CarVolumeGroup[] volumeGroups = zones[0].getVolumeGroups();
+        CarVolumeGroup[] volumeGroups = zones.get(0).getVolumeGroups();
         CarVolumeGroup mediaVolumeGroup = volumeGroups[0];
         List<Integer> contexts = IntStream.of(mediaVolumeGroup.getContexts()).boxed().collect(
                 Collectors.toList());
@@ -146,9 +147,9 @@
         CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
                 carAudioDeviceInfos, mMockAudioControlWrapper, mMockCarAudioSettings);
 
-        CarAudioZone[] zones = helper.loadAudioZones();
+        SparseArray<CarAudioZone> zones = helper.loadAudioZones();
 
-        CarVolumeGroup[] volumeGroups = zones[0].getVolumeGroups();
+        CarVolumeGroup[] volumeGroups = zones.get(0).getVolumeGroups();
         CarVolumeGroup mediaVolumeGroup = volumeGroups[0];
         List<Integer> contexts = IntStream.of(mediaVolumeGroup.getContexts()).boxed().collect(
                 Collectors.toList());
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 fce0a78..87cefb8 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -43,6 +44,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -137,9 +139,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        List<CarAudioZone> zoneList = Arrays.asList(cazh.loadAudioZones());
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        assertThat(zoneList).hasSize(2);
+        assertThat(zones.size()).isEqualTo(2);
     }
 
     @Test
@@ -149,9 +151,9 @@
             CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, versionOneStream,
                     mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-            CarAudioZone[] zones = cazh.loadAudioZones();
+            SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-            assertThat(zones.length).isEqualTo(2);
+            assertThat(zones.size()).isEqualTo(2);
         }
     }
 
@@ -160,12 +162,13 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
+
 
         List<Integer> zoneIds = getListOfZoneIds(zones);
-        assertThat(zoneIds.size()).isEqualTo(2);
-        assertThat(zoneIds)
-                .containsExactly(CarAudioManager.PRIMARY_AUDIO_ZONE, SECONDARY_ZONE_ID).inOrder();
+        assertThat(zones.size()).isEqualTo(2);
+        assertThat(zones.contains(CarAudioManager.PRIMARY_AUDIO_ZONE)).isTrue();
+        assertThat(zones.contains(SECONDARY_ZONE_ID)).isTrue();
     }
 
     @Test
@@ -173,9 +176,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        assertThat(zones.length).isEqualTo(2);
+        assertThat(zones.size()).isEqualTo(2);
 
         SparseIntArray audioZoneIdToOccupantZoneIdMapping =
                 cazh.getCarAudioZoneIdToOccupantZoneIdMapping();
@@ -190,9 +193,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone primaryZone = zones[0];
+        CarAudioZone primaryZone = zones.get(0);
         assertThat(primaryZone.getName()).isEqualTo(PRIMARY_ZONE_NAME);
     }
 
@@ -201,12 +204,12 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone primaryZone = zones[0];
+        CarAudioZone primaryZone = zones.get(0);
         assertThat(primaryZone.isPrimaryZone()).isTrue();
 
-        CarAudioZone rseZone = zones[1];
+        CarAudioZone rseZone = zones.get(2);
         assertThat(rseZone.isPrimaryZone()).isFalse();
     }
 
@@ -215,9 +218,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone primaryZone = zones[0];
+        CarAudioZone primaryZone = zones.get(0);
         assertThat(primaryZone.getVolumeGroupCount()).isEqualTo(2);
     }
 
@@ -226,9 +229,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone primaryZone = zones[0];
+        CarAudioZone primaryZone = zones.get(0);
         CarVolumeGroup volumeGroup = primaryZone.getVolumeGroups()[0];
         List<String> addresses = volumeGroup.getAddresses();
         assertThat(addresses).containsExactly(BUS_0_ADDRESS, BUS_3_ADDRESS);
@@ -239,14 +242,14 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone primaryZone = zones[0];
+        CarAudioZone primaryZone = zones.get(0);
         CarVolumeGroup volumeGroup = primaryZone.getVolumeGroups()[0];
         assertThat(volumeGroup.getContextsForAddress(BUS_0_ADDRESS)).asList()
                 .containsExactly(CarAudioContext.MUSIC);
 
-        CarAudioZone rearSeatEntertainmentZone = zones[1];
+        CarAudioZone rearSeatEntertainmentZone = zones.get(2);
         CarVolumeGroup rseVolumeGroup = rearSeatEntertainmentZone.getVolumeGroups()[0];
         List<Integer> contextForBus100List =
                 Arrays.stream(rseVolumeGroup.getContextsForAddress(BUS_100_ADDRESS))
@@ -264,9 +267,9 @@
         CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, versionOneStream,
                 mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-        CarAudioZone[] zones = cazh.loadAudioZones();
+        SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-        CarAudioZone defaultZone = zones[0];
+        CarAudioZone defaultZone = zones.get(0);
         CarVolumeGroup volumeGroup = defaultZone.getVolumeGroups()[0];
         List<Integer> audioContexts = Arrays.stream(volumeGroup.getContexts()).boxed()
                 .collect(Collectors.toList());
@@ -299,12 +302,11 @@
                     new CarAudioZonesHelper(mCarAudioSettings, missingAudioZoneIdStream,
                             mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-            CarAudioZone[] zones = cazh.loadAudioZones();
+            SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-            List<Integer> zoneIds = getListOfZoneIds(zones);
-            assertThat(zoneIds.size()).isEqualTo(2);
-            assertThat(zoneIds).contains(CarAudioManager.PRIMARY_AUDIO_ZONE);
-            assertThat(zoneIds).contains(SECONDARY_ZONE_ID);
+            assertThat(zones.size()).isEqualTo(2);
+            assertThat(zones.contains(CarAudioManager.PRIMARY_AUDIO_ZONE)).isTrue();
+            assertThat(zones.contains(SECONDARY_ZONE_ID)).isTrue();
         }
     }
 
@@ -344,9 +346,9 @@
                     new CarAudioZonesHelper(mCarAudioSettings, inputDevicesStream,
                             mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
 
-            CarAudioZone[] zones = cazh.loadAudioZones();
+            SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
 
-            CarAudioZone primaryZone = zones[0];
+            CarAudioZone primaryZone = zones.get(0);
             List<AudioDeviceAttributes> primaryZoneInputDevices =
                     primaryZone.getInputAudioDevices();
             assertThat(primaryZoneInputDevices).hasSize(2);
@@ -357,7 +359,7 @@
             assertThat(primaryZoneInputAddresses).containsExactly(PRIMARY_ZONE_FM_TUNER_ADDRESS,
                     PRIMARY_ZONE_MICROPHONE_ADDRESS).inOrder();
 
-            CarAudioZone secondaryZone = zones[1];
+            CarAudioZone secondaryZone = zones.get(1);
             List<AudioDeviceAttributes> secondaryZoneInputDevices =
                     secondaryZone.getInputAudioDevices();
             List<String> secondaryZoneInputAddresses =
@@ -570,7 +572,11 @@
         }
     }
 
-    private List<Integer> getListOfZoneIds(CarAudioZone[] zones) {
-        return Arrays.stream(zones).map(CarAudioZone::getId).collect(Collectors.toList());
+    private List<Integer> getListOfZoneIds(SparseArray<CarAudioZone> zones) {
+        List<Integer> zoneIds = new ArrayList<>();
+        for (int i = 0; i < zones.size(); i++) {
+            zoneIds.add(zones.keyAt(i));
+        }
+        return zoneIds;
     }
 }
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
index c171321..9c32893 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesValidatorTest.java
@@ -17,9 +17,10 @@
 
 import static org.mockito.Mockito.when;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import android.car.media.CarAudioManager;
+import android.util.SparseArray;
 
-import com.google.common.collect.Lists;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -27,6 +28,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 public class CarAudioZonesValidatorTest {
     @Rule
@@ -37,52 +40,102 @@
         thrown.expect(RuntimeException.class);
         thrown.expectMessage("At least one zone should be defined");
 
-        CarAudioZonesValidator.validate(new CarAudioZone[0]);
+        CarAudioZonesValidator.validate(new SparseArray<CarAudioZone>());
     }
 
     @Test
     public void validate_volumeGroupsForEachZone() {
-        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
-        when(primaryZone.validateVolumeGroups()).thenReturn(true);
-        CarAudioZone zoneOne = Mockito.mock(CarAudioZone.class);
-        when(zoneOne.validateVolumeGroups()).thenReturn(false);
-        when(zoneOne.getId()).thenReturn(1);
+        SparseArray<CarAudioZone> zones = generateAudioZonesWithPrimary();
+        CarAudioZone zoneOne = new MockBuilder()
+                .withInvalidVolumeGroups()
+                .withZoneId(1)
+                .build();
+        zones.put(zoneOne.getId(), zoneOne);
 
         thrown.expect(RuntimeException.class);
         thrown.expectMessage("Invalid volume groups configuration for zone " + 1);
 
-        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone, zoneOne});
+        CarAudioZonesValidator.validate(zones);
     }
 
     @Test
     public void validate_eachAddressAppearsInOnlyOneZone() {
-        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
-        CarVolumeGroup mockVolumeGroup = Mockito.mock(CarVolumeGroup.class);
-        when(mockVolumeGroup.getAddresses()).thenReturn(Lists.newArrayList("one", "two", "three"));
-        when(primaryZone.getVolumeGroups()).thenReturn(new CarVolumeGroup[]{mockVolumeGroup});
-        when(primaryZone.validateVolumeGroups()).thenReturn(true);
+        CarVolumeGroup mockVolumeGroup = generateVolumeGroup(List.of("one", "two", "three"));
 
-        CarAudioZone secondaryZone = Mockito.mock(CarAudioZone.class);
-        CarVolumeGroup mockSecondaryVolmeGroup = Mockito.mock(CarVolumeGroup.class);
-        when(mockSecondaryVolmeGroup.getAddresses()).thenReturn(
-                Lists.newArrayList("three", "four", "five"));
-        when(secondaryZone.getVolumeGroups()).thenReturn(
-                new CarVolumeGroup[]{mockSecondaryVolmeGroup});
-        when(secondaryZone.validateVolumeGroups()).thenReturn(true);
+        CarAudioZone primaryZone = new MockBuilder()
+                .withVolumeGroups(new CarVolumeGroup[]{mockVolumeGroup})
+                .build();
+
+        CarVolumeGroup mockSecondaryVolumeGroup = generateVolumeGroup(
+                List.of("three", "four", "five"));
+
+        CarAudioZone secondaryZone = new MockBuilder()
+                .withZoneId(1)
+                .withVolumeGroups(new CarVolumeGroup[]{mockSecondaryVolumeGroup})
+                .build();
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(primaryZone.getId(), primaryZone);
+        zones.put(secondaryZone.getId(), secondaryZone);
+
 
         thrown.expect(RuntimeException.class);
         thrown.expectMessage(
                 "Device with address three appears in multiple volume groups or audio zones");
 
-        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone, secondaryZone});
+        CarAudioZonesValidator.validate(zones);
     }
 
     @Test
     public void validate_passesWithoutExceptionForValidZoneConfiguration() {
-        CarAudioZone primaryZone = Mockito.mock(CarAudioZone.class);
-        when(primaryZone.validateVolumeGroups()).thenReturn(true);
-        when(primaryZone.getVolumeGroups()).thenReturn(new CarVolumeGroup[0]);
+        SparseArray<CarAudioZone> zones = generateAudioZonesWithPrimary();
 
-        CarAudioZonesValidator.validate(new CarAudioZone[]{primaryZone});
+        CarAudioZonesValidator.validate(zones);
     }
-}
+
+    private SparseArray<CarAudioZone> generateAudioZonesWithPrimary() {
+        CarAudioZone zone = new MockBuilder().build();
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(zone.getId(), zone);
+        return zones;
+    }
+
+    private CarVolumeGroup generateVolumeGroup(List<String> deviceAddresses) {
+        CarVolumeGroup mockVolumeGroup = Mockito.mock(CarVolumeGroup.class);
+        when(mockVolumeGroup.getAddresses()).thenReturn(deviceAddresses);
+        return mockVolumeGroup;
+    }
+
+    private CarAudioZone getMockPrimaryZone() {
+        CarAudioZone zoneMock = Mockito.mock(CarAudioZone.class);
+        when(zoneMock.getId()).thenReturn(CarAudioManager.PRIMARY_AUDIO_ZONE);
+        return zoneMock;
+    }
+    private static class MockBuilder {
+        private boolean mHasValidVolumeGroups = true;
+        private int mZoneId = 0;
+        private CarVolumeGroup[] mVolumeGroups = new CarVolumeGroup[0];
+
+        CarAudioZone build() {
+            CarAudioZone zoneMock = Mockito.mock(CarAudioZone.class);
+            when(zoneMock.getId()).thenReturn(mZoneId);
+            when(zoneMock.validateVolumeGroups()).thenReturn(mHasValidVolumeGroups);
+            when(zoneMock.getVolumeGroups()).thenReturn(mVolumeGroups);
+            return zoneMock;
+        }
+
+        MockBuilder withInvalidVolumeGroups() {
+            mHasValidVolumeGroups = false;
+            return this;
+        }
+
+        MockBuilder withZoneId(int zoneId) {
+            mZoneId = zoneId;
+            return this;
+        }
+
+        MockBuilder withVolumeGroups(CarVolumeGroup[] volumeGroups) {
+            mVolumeGroups = volumeGroups;
+            return this;
+        }
+    }
+}
\ No newline at end of file
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 7a3f813..984d9e0 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
@@ -27,6 +27,7 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -40,10 +41,10 @@
 import android.media.audiopolicy.AudioPolicy;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.SparseArray;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,27 +72,14 @@
     @Mock
     private AudioManager mMockAudioManager;
     @Mock
-    private PackageManager mMockPackageManager;
-    @Mock
     private AudioPolicy mAudioPolicy;
     @Mock
-    private CarAudioZone mPrimaryAudioZone;
-    @Mock
-    private CarAudioZone mSecondaryAudioZone;
-    @Mock
     private CarAudioService mCarAudioService;
     @Mock
     private ContentResolver mContentResolver;
     @Mock
     private CarAudioSettings mCarAudioSettings;
 
-    private CarAudioZone[] mMockAudioZones;
-
-    @Before
-    public void setUp() {
-        mMockAudioZones = generateAudioZones();
-    }
-
     @Test
     public void onAudioFocusRequest_withNoCurrentFocusHolder_requestGranted() {
         CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false);
@@ -303,18 +291,22 @@
                 .setFocusRequestResult(audioFocusClient, expectedAudioFocusResults, mAudioPolicy);
     }
 
-    private CarAudioZone[] generateAudioZones() {
-        mPrimaryAudioZone = new CarAudioZone(PRIMARY_ZONE_ID, "Primary zone");
-        mSecondaryAudioZone = new CarAudioZone(SECONDARY_ZONE_ID, "Secondary zone");
-        CarAudioZone[] zones = {mPrimaryAudioZone, mSecondaryAudioZone};
+    private SparseArray<CarAudioZone> generateAudioZones() {
+        SparseArray<CarAudioZone> zones = new SparseArray<>();
+        zones.put(PRIMARY_ZONE_ID, new CarAudioZone(PRIMARY_ZONE_ID, "Primary zone"));
+        zones.put(SECONDARY_ZONE_ID, new CarAudioZone(SECONDARY_ZONE_ID, "Secondary zone"));
         return zones;
     }
 
     private CarZonesAudioFocus getCarZonesAudioFocus(boolean enableDelayedFocus) {
+        SparseArray<CarAudioZone> zones = generateAudioZones();
+        PackageManager mockPackageManager = mock(PackageManager.class);
         CarZonesAudioFocus carZonesAudioFocus =
-                new CarZonesAudioFocus(mMockAudioManager, mMockPackageManager,
-                        mMockAudioZones, mCarAudioSettings, enableDelayedFocus);
+                new CarZonesAudioFocus(mMockAudioManager, mockPackageManager, zones,
+                        mCarAudioSettings, enableDelayedFocus);
         carZonesAudioFocus.setOwningPolicy(mCarAudioService, mAudioPolicy);
+
+
         return carZonesAudioFocus;
     }