Added dynamic audio zone API and interface

Added APIs in car audio manager and car audio interface to get current
car audio zone configuration, get all configurations available, and
switch configuration. The car audio configuration info was defined in
CarAudioZoneConfigInfo, which should not be created or modified by
applications.

Bug: 262438891
API-Coverage-Bug: 268352501
Test: atest com.android.car.audio
Change-Id: I214b685b909305c81c2fac40ad2a9c8effaf1545
diff --git a/car-lib-module/api/system-current.txt b/car-lib-module/api/system-current.txt
index 5ee48b0..6254f54 100644
--- a/car-lib-module/api/system-current.txt
+++ b/car-lib-module/api/system-current.txt
@@ -1172,7 +1172,9 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void clearPrimaryZoneMediaAudioRequestCallback();
     method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public java.util.List<android.media.AudioAttributes> getAudioAttributesForVolumeGroup(@NonNull android.car.media.CarVolumeGroupInfo);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<android.car.media.CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<java.lang.Integer> getAudioZoneIds();
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int);
     method @Deprecated @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public String[] getExternalSources();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
@@ -1202,6 +1204,7 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public boolean setPrimaryZoneMediaAudioRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.media.PrimaryZoneMediaAudioRequestCallback);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setVolumeGroupMute(int, int, boolean, int);
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void switchAudioZoneToConfig(@NonNull android.car.media.CarAudioZoneConfigInfo, @NonNull java.util.concurrent.Executor, @NonNull android.car.media.SwitchAudioZoneConfigCallback);
     field public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
     field public static final int AUDIO_REQUEST_STATUS_APPROVED = 1; // 0x1
     field public static final int AUDIO_REQUEST_STATUS_CANCELLED = 3; // 0x3
@@ -1218,6 +1221,15 @@
     field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
   }
 
+  public final class CarAudioZoneConfigInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getId();
+    method @NonNull public String getName();
+    method public int getZoneId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.media.CarAudioZoneConfigInfo> CREATOR;
+  }
+
   public final class CarMediaManager {
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public java.util.List<android.content.ComponentName> getLastMediaSources(int);
@@ -1269,6 +1281,10 @@
     method public void onRequestMediaOnPrimaryZone(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo, long);
   }
 
+  public interface SwitchAudioZoneConfigCallback {
+    method public void onAudioZoneConfigSwitched(@NonNull android.car.media.CarAudioZoneConfigInfo, boolean);
+  }
+
 }
 
 package android.car.navigation {
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 5ee48b0..6254f54 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -1172,7 +1172,9 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void clearPrimaryZoneMediaAudioRequestCallback();
     method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public java.util.List<android.media.AudioAttributes> getAudioAttributesForVolumeGroup(@NonNull android.car.media.CarVolumeGroupInfo);
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<android.car.media.CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<java.lang.Integer> getAudioZoneIds();
+    method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int);
     method @Deprecated @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public String[] getExternalSources();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
@@ -1202,6 +1204,7 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public boolean setPrimaryZoneMediaAudioRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.media.PrimaryZoneMediaAudioRequestCallback);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setVolumeGroupMute(int, int, boolean, int);
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void switchAudioZoneToConfig(@NonNull android.car.media.CarAudioZoneConfigInfo, @NonNull java.util.concurrent.Executor, @NonNull android.car.media.SwitchAudioZoneConfigCallback);
     field public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
     field public static final int AUDIO_REQUEST_STATUS_APPROVED = 1; // 0x1
     field public static final int AUDIO_REQUEST_STATUS_CANCELLED = 3; // 0x3
@@ -1218,6 +1221,15 @@
     field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
   }
 
+  public final class CarAudioZoneConfigInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getId();
+    method @NonNull public String getName();
+    method public int getZoneId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.car.media.CarAudioZoneConfigInfo> CREATOR;
+  }
+
   public final class CarMediaManager {
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public java.util.List<android.content.ComponentName> getLastMediaSources(int);
@@ -1269,6 +1281,10 @@
     method public void onRequestMediaOnPrimaryZone(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo, long);
   }
 
+  public interface SwitchAudioZoneConfigCallback {
+    method public void onAudioZoneConfigSwitched(@NonNull android.car.media.CarAudioZoneConfigInfo, boolean);
+  }
+
 }
 
 package android.car.navigation {
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 386c4ec..26cce6d 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -1243,7 +1243,9 @@
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void clearPrimaryZoneMediaAudioRequestCallback();
     method @Deprecated @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioPatchHandle createAudioPatch(String, int, int);
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.TIRAMISU_3, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.TIRAMISU_0) @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public java.util.List<android.media.AudioAttributes> getAudioAttributesForVolumeGroup(@NonNull android.car.media.CarVolumeGroupInfo);
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<android.car.media.CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int);
     method @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public java.util.List<java.lang.Integer> getAudioZoneIds();
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public android.car.media.CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int);
     method @Deprecated @NonNull @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public String[] getExternalSources();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public int getGroupMaxVolume(int, int);
@@ -1274,6 +1276,7 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setGroupVolume(int, int, int, int);
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public boolean setPrimaryZoneMediaAudioRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.media.PrimaryZoneMediaAudioRequestCallback);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) public void setVolumeGroupMute(int, int, boolean, int);
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @RequiresPermission(android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) public void switchAudioZoneToConfig(@NonNull android.car.media.CarAudioZoneConfigInfo, @NonNull java.util.concurrent.Executor, @NonNull android.car.media.SwitchAudioZoneConfigCallback);
     field public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
     field @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public static final int AUDIO_REQUEST_STATUS_APPROVED = 1; // 0x1
     field @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public static final int AUDIO_REQUEST_STATUS_CANCELLED = 3; // 0x3
@@ -1290,6 +1293,15 @@
     field public static final android.os.Parcelable.Creator<android.car.media.CarAudioPatchHandle> CREATOR;
   }
 
+  @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public final class CarAudioZoneConfigInfo implements android.os.Parcelable {
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public int describeContents();
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public int getId();
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @NonNull public String getName();
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public int getZoneId();
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) @NonNull public static final android.os.Parcelable.Creator<android.car.media.CarAudioZoneConfigInfo> CREATOR;
+  }
+
   public final class CarMediaManager {
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addMediaSourceListener(@NonNull android.car.media.CarMediaManager.MediaSourceChangedListener, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public java.util.List<android.content.ComponentName> getLastMediaSources(int);
@@ -1341,6 +1353,10 @@
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public void onRequestMediaOnPrimaryZone(@NonNull android.car.CarOccupantZoneManager.OccupantZoneInfo, long);
   }
 
+  @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public interface SwitchAudioZoneConfigCallback {
+    method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) public void onAudioZoneConfigSwitched(@NonNull android.car.media.CarAudioZoneConfigInfo, boolean);
+  }
+
 }
 
 package android.car.navigation {
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 151da1b..be2e69b 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -790,6 +790,91 @@
     }
 
     /**
+     * Returns the current car audio zone configuration info associated with the zone id
+     *
+     * <p>If the car audio configuration does not include zone configurations, a default
+     * configuration consisting current output devices for the zone is returned.
+     *
+     * @param zoneId Zone id for the configuration to query
+     * @return the current car audio zone configuration info
+     * @throws IllegalStateException if dynamic audio routing is not enabled
+     * @throws IllegalArgumentException if the audio zone id is invalid
+     *
+     * @hide
+     */
+    @SystemApi
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    @NonNull
+    public CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int zoneId) {
+        try {
+            return mService.getCurrentAudioZoneConfigInfo(zoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, null);
+        }
+    }
+
+    /**
+     * Returns a list of car audio zone configuration info associated with the zone id
+     *
+     * <p>If the car audio configuration does not include zone configurations, a default
+     * configuration consisting current output devices for each zone is returned.
+     *
+     * @param zoneId Zone id for the configuration to query
+     * @return all the car audio zone configuration info for the zone id
+     * @throws IllegalStateException if dynamic audio routing is not enabled
+     * @throws IllegalArgumentException if the audio zone id is invalid
+     *
+     * @hide
+     */
+    @SystemApi
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    @NonNull
+    public List<CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int zoneId) {
+        try {
+            return mService.getAudioZoneConfigInfos(zoneId);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+        }
+    }
+
+    /**
+     * Switches the car audio zone configuration
+     *
+     * @param zoneConfig Audio zone configuration to switch to
+     * @param executor Executor on which callback will be invoked
+     * @param callback Callback that will report the result of switching car audio zone
+     *                 configuration
+     * @throws NullPointerException if either executor or callback are {@code null}
+     * @throws IllegalStateException if dynamic audio routing is not enabled
+     * @throws IllegalArgumentException if the audio zone configuration is invalid
+     *
+     * @hide
+     */
+    @SystemApi
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+    public void switchAudioZoneToConfig(@NonNull CarAudioZoneConfigInfo zoneConfig,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SwitchAudioZoneConfigCallback callback) {
+        Objects.requireNonNull(zoneConfig, "Audio zone configuration can not be null");
+        Objects.requireNonNull(executor, "Executor can not be null");
+        Objects.requireNonNull(callback,
+                "Switching audio zone configuration result callback can not be null");
+        SwitchAudioZoneConfigCallbackWrapper wrapper =
+                new SwitchAudioZoneConfigCallbackWrapper(executor, callback);
+        try {
+            mService.switchZoneToConfig(zoneConfig, wrapper);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
      * Gets the audio zones currently available
      *
      * @return audio zone ids
@@ -1431,4 +1516,28 @@
             }
         }
     }
+
+    private static final class SwitchAudioZoneConfigCallbackWrapper
+            extends ISwitchAudioZoneConfigCallback.Stub {
+        private final Executor mExecutor;
+        private final SwitchAudioZoneConfigCallback mCallback;
+
+        SwitchAudioZoneConfigCallbackWrapper(Executor executor,
+                SwitchAudioZoneConfigCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAudioZoneConfigSwitched(CarAudioZoneConfigInfo zoneConfig,
+                boolean isSuccessful) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() ->
+                        mCallback.onAudioZoneConfigSwitched(zoneConfig, isSuccessful));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
 }
diff --git a/car-lib/src/android/car/media/CarAudioZoneConfigInfo.aidl b/car-lib/src/android/car/media/CarAudioZoneConfigInfo.aidl
new file mode 100644
index 0000000..7696fc8
--- /dev/null
+++ b/car-lib/src/android/car/media/CarAudioZoneConfigInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.car.media;
+
+/**
+ * Class to encapsulate car audio zone configuration information.
+ *
+ * @hide
+ */
+parcelable CarAudioZoneConfigInfo;
diff --git a/car-lib/src/android/car/media/CarAudioZoneConfigInfo.java b/car-lib/src/android/car/media/CarAudioZoneConfigInfo.java
new file mode 100644
index 0000000..e5d56a9
--- /dev/null
+++ b/car-lib/src/android/car/media/CarAudioZoneConfigInfo.java
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+package android.car.media;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.car.annotation.ApiRequirements;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Class to encapsulate car audio zone configuration information.
+ *
+ * @hide
+ */
+@SystemApi
+@ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+        minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+public final class CarAudioZoneConfigInfo implements Parcelable {
+
+    private final String mName;
+    private final int mZoneId;
+    private final int mConfigInfoId;
+
+    /**
+     * Constructor of car audio zone configuration info
+     *
+     * @param name Name for car audio zone configuration info
+     * @param zoneId Id of car audio zone
+     * @param configInfoId Id of car audio zone configuration info
+     *
+     * @hide
+     */
+    public CarAudioZoneConfigInfo(String name, int zoneId, int configInfoId) {
+        mName = Objects.requireNonNull(name, "Zone configuration name can not be null");
+        mZoneId = zoneId;
+        mConfigInfoId = configInfoId;
+    }
+
+    /**
+     * Creates zone configuration info from parcel
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public CarAudioZoneConfigInfo(Parcel in) {
+        mName = in.readString();
+        mZoneId = in.readInt();
+        mConfigInfoId = in.readInt();
+    }
+
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    @NonNull
+    public static final Creator<CarAudioZoneConfigInfo> CREATOR = new Creator<>() {
+        @Override
+        @NonNull
+        public CarAudioZoneConfigInfo createFromParcel(@NonNull Parcel in) {
+            return new CarAudioZoneConfigInfo(in);
+        }
+
+        @Override
+        @NonNull
+        public CarAudioZoneConfigInfo[] newArray(int size) {
+            return new CarAudioZoneConfigInfo[size];
+        }
+    };
+
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the car audio zone configuration name
+     */
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the car audio zone id
+     */
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public int getZoneId() {
+        return mZoneId;
+    }
+
+    /**
+     * Returns the car audio zone configuration id
+     */
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public int getId() {
+        return mConfigInfoId;
+    }
+
+    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public String toString() {
+        return new StringBuilder().append("CarVolumeGroupId { .name = ").append(mName)
+                .append(", zone id = ").append(mZoneId).append(" id = ").append(mConfigInfoId)
+                .append(" }").toString();
+    }
+
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeInt(mZoneId);
+        dest.writeInt(mConfigInfoId);
+    }
+
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof CarAudioZoneConfigInfo)) {
+            return false;
+        }
+
+        CarAudioZoneConfigInfo that = (CarAudioZoneConfigInfo) o;
+
+        return mName.equals(that.mName) && mZoneId == that.mZoneId
+                && mConfigInfoId == that.mConfigInfoId;
+    }
+
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    public int hashCode() {
+        return Objects.hash(mName, mZoneId, mConfigInfoId);
+    }
+}
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index c2d0d08..9afd810 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -18,9 +18,11 @@
 
 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
 import android.car.media.CarAudioPatchHandle;
+import android.car.media.CarAudioZoneConfigInfo;
 import android.car.media.CarVolumeGroupInfo;
 import android.car.media.IMediaAudioRequestStatusCallback;
 import android.car.media.IPrimaryZoneMediaAudioRequestCallback;
+import android.car.media.ISwitchAudioZoneConfigCallback;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 
@@ -80,6 +82,11 @@
     boolean isMediaAudioAllowedInPrimaryZone(in OccupantZoneInfo info);
     boolean resetMediaAudioOnPrimaryZone(in OccupantZoneInfo zone);
 
+    CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int zoneId);
+    List<CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int zoneId);
+    void switchZoneToConfig(in CarAudioZoneConfigInfo zoneConfig,
+            in ISwitchAudioZoneConfigCallback callback);
+
     /**
      * IBinder is ICarVolumeCallback but passed as IBinder due to aidl hidden.
      */
diff --git a/car-lib/src/android/car/media/ISwitchAudioZoneConfigCallback.aidl b/car-lib/src/android/car/media/ISwitchAudioZoneConfigCallback.aidl
new file mode 100644
index 0000000..7d90e47
--- /dev/null
+++ b/car-lib/src/android/car/media/ISwitchAudioZoneConfigCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.car.media;
+
+import android.car.media.CarAudioZoneConfigInfo;
+
+/**
+ * Binder interface to listen to switching car audio zone configuration request results
+ *
+ * @hide
+ */
+oneway interface ISwitchAudioZoneConfigCallback {
+    /**
+     * Called when the car audio zone configuration is switched
+     *
+     * @param zoneConfig Car audio zone configuration to switch to
+     * @param isSuccessful {@code true} if the audio config change is successful, {@code false}
+     * otherwise
+     */
+    void onAudioZoneConfigSwitched(in CarAudioZoneConfigInfo zoneConfig,
+            boolean isSuccessful);
+}
diff --git a/car-lib/src/android/car/media/SwitchAudioZoneConfigCallback.java b/car-lib/src/android/car/media/SwitchAudioZoneConfigCallback.java
new file mode 100644
index 0000000..134c20d
--- /dev/null
+++ b/car-lib/src/android/car/media/SwitchAudioZoneConfigCallback.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.car.media;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.car.annotation.ApiRequirements;
+
+/**
+ * Callback to informed about car audio zone configuration request results
+ *
+ * @hide
+ */
+@ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+        minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+@SystemApi
+public interface SwitchAudioZoneConfigCallback {
+    /**
+     * Called when the car audio zone configuration is switched
+     *
+     * @param zoneConfig Car audio zone configuration to switch to
+     * @param isSuccessful {@code true} if the audio config change is successful, {@code false}
+     * otherwise (i.e. failed to register an audio policy to the audio service)
+     */
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
+            minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
+    void onAudioZoneConfigSwitched(@NonNull CarAudioZoneConfigInfo zoneConfig,
+            boolean isSuccessful);
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCarAudioService.java b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
index f313b4b..9713f74 100644
--- a/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
+++ b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
@@ -20,10 +20,12 @@
 
 import android.car.CarOccupantZoneManager;
 import android.car.media.CarAudioPatchHandle;
+import android.car.media.CarAudioZoneConfigInfo;
 import android.car.media.CarVolumeGroupInfo;
 import android.car.media.ICarAudio;
 import android.car.media.IMediaAudioRequestStatusCallback;
 import android.car.media.IPrimaryZoneMediaAudioRequestCallback;
+import android.car.media.ISwitchAudioZoneConfigCallback;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.os.IBinder;
@@ -197,6 +199,21 @@
     }
 
     @Override
+    public CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int audioZoneId) {
+        return null;
+    }
+
+    @Override
+    public List<CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int audioZoneId) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void switchZoneToConfig(CarAudioZoneConfigInfo zoneConfig,
+            ISwitchAudioZoneConfigCallback callback) {
+    }
+
+    @Override
     public void registerVolumeCallback(IBinder binder) {
     }
 
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index d0856f2..c4fa049 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -58,11 +58,13 @@
 import android.car.builtin.util.Slogf;
 import android.car.media.CarAudioManager;
 import android.car.media.CarAudioPatchHandle;
+import android.car.media.CarAudioZoneConfigInfo;
 import android.car.media.CarVolumeGroupInfo;
 import android.car.media.ICarAudio;
 import android.car.media.ICarVolumeCallback;
 import android.car.media.IMediaAudioRequestStatusCallback;
 import android.car.media.IPrimaryZoneMediaAudioRequestCallback;
+import android.car.media.ISwitchAudioZoneConfigCallback;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
@@ -1697,6 +1699,24 @@
         return getCarAudioZone(zoneId).getInputAudioDevices();
     }
 
+    @Override
+    public CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int zoneId) {
+        // TODO(b/268383539): implement getting current zone config info in car audio service.
+        return null;
+    }
+
+    @Override
+    public List<CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int zoneId) {
+        // TODO(b/268383539): implement getting all zone config infos in car audio service.
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void switchZoneToConfig(CarAudioZoneConfigInfo zoneConfig,
+            ISwitchAudioZoneConfigCallback callback) {
+        // TODO(b/268383539): implement switching zone config infos in car audio service.
+    }
+
     void setAudioEnabled(boolean isAudioEnabled) {
         Slogf.i(TAG, "Setting isAudioEnabled to %b", isAudioEnabled);
 
diff --git a/tests/carservice_unit_test/res/raw/car_api_classes.txt b/tests/carservice_unit_test/res/raw/car_api_classes.txt
index c70e7f4..238c271 100644
--- a/tests/carservice_unit_test/res/raw/car_api_classes.txt
+++ b/tests/carservice_unit_test/res/raw/car_api_classes.txt
@@ -183,6 +183,7 @@
 android.car.media.CarAudioManager
 android.car.media.CarAudioManager$CarVolumeCallback
 android.car.media.CarAudioPatchHandle
+android.car.media.CarAudioZoneConfigInfo
 android.car.media.CarMediaIntents
 android.car.media.CarMediaManager
 android.car.media.CarMediaManager$MediaSourceChangedListener
@@ -190,6 +191,7 @@
 android.car.media.CarVolumeGroupInfo$Builder
 android.car.media.MediaAudioRequestStatusCallback
 android.car.media.PrimaryZoneMediaAudioRequestCallback
+android.car.media.SwitchAudioZoneConfigCallback
 android.car.navigation.CarNavigationInstrumentCluster
 android.car.navigation.CarNavigationStatusManager
 android.car.occupantawareness.DriverMonitoringDetection