[automerger skipped] Mark ab/8389834 as merged am: a74294fe1f -s ours am: 341369e970 -s ours am: ad8fd652c1 -s ours am: 573fa5f473 -s ours am: 281b8f2b76 -s ours am: c31290116a -s ours am: e13224af1e -s ours

am skip reason: Merged-In I890f54f80ff3aca3dcb2c51f20db087d453c2927 with SHA-1 cefd18a941 is already in history

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2052770

Change-Id: I1226a3bd877cea2d9391f093d980cb9900ae0800
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index 46d177f..05e22bf 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -67,3 +67,33 @@
     ],
 
 }
+
+aidl_interface {
+    name: "android.hardware.audio.core",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/audio/core/AudioPatch.aidl",
+        "android/hardware/audio/core/AudioRoute.aidl",
+        "android/hardware/audio/core/IConfig.aidl",
+        "android/hardware/audio/core/IModule.aidl",
+        "android/hardware/audio/core/IStreamIn.aidl",
+        "android/hardware/audio/core/IStreamOut.aidl",
+    ],
+    imports: [
+        "android.hardware.audio.common-V1",
+        "android.media.audio.common.types",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+    versions: [
+    ],
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl
new file mode 100644
index 0000000..1cef4cd
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPatch {
+  int id;
+  int[] sourcePortConfigIds;
+  int[] sinkPortConfigIds;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl
new file mode 100644
index 0000000..deeef87
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioRoute {
+  int[] sourcePortIds;
+  int sinkPortId;
+  boolean isExclusive;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl
new file mode 100644
index 0000000..fd80715
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@VintfStability
+interface IConfig {
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
new file mode 100644
index 0000000..33e8290
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@VintfStability
+interface IModule {
+  android.hardware.audio.core.AudioPatch[] getAudioPatches();
+  android.media.audio.common.AudioPort getAudioPort(int portId);
+  android.media.audio.common.AudioPortConfig[] getAudioPortConfigs();
+  android.media.audio.common.AudioPort[] getAudioPorts();
+  android.hardware.audio.core.AudioRoute[] getAudioRoutes();
+  android.hardware.audio.core.IStreamIn openInputStream(int portConfigId, in android.hardware.audio.common.SinkMetadata sinkMetadata);
+  android.hardware.audio.core.IStreamOut openOutputStream(int portConfigId, in android.hardware.audio.common.SourceMetadata sourceMetadata, in @nullable android.media.audio.common.AudioOffloadInfo offloadInfo);
+  android.hardware.audio.core.AudioPatch setAudioPatch(in android.hardware.audio.core.AudioPatch requested);
+  boolean setAudioPortConfig(in android.media.audio.common.AudioPortConfig requested, out android.media.audio.common.AudioPortConfig suggested);
+  void resetAudioPatch(int patchId);
+  void resetAudioPortConfig(int portConfigId);
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl
new file mode 100644
index 0000000..d5ab3e8
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@VintfStability
+interface IStreamIn {
+  void close();
+  void updateMetadata(in android.hardware.audio.common.SinkMetadata sinkMetadata);
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl
new file mode 100644
index 0000000..3021d94
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@VintfStability
+interface IStreamOut {
+  void close();
+  void updateMetadata(in android.hardware.audio.common.SourceMetadata sourceMetadata);
+}
diff --git a/audio/aidl/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl
new file mode 100644
index 0000000..48ca214
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+/**
+ * Audio patch specifies a connection between multiple audio port
+ * configurations.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioPatch {
+    /** The ID of the patch, unique within the HAL module. */
+    int id;
+    /**
+     * The list of IDs of source audio port configs ('AudioPortConfig.id').
+     * There must be at least one source in a valid patch and all IDs must be
+     * unique.
+     */
+    int[] sourcePortConfigIds;
+    /**
+     * The list of IDs of sink audio port configs ('AudioPortConfig.id').
+     * There must be at least one sink in a valid patch and all IDs must be
+     * unique.
+     */
+    int[] sinkPortConfigIds;
+}
diff --git a/audio/aidl/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/android/hardware/audio/core/AudioRoute.aidl
new file mode 100644
index 0000000..1e7b441
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/AudioRoute.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+/**
+ * Audio route specifies a path from multiple audio source ports to one audio
+ * sink port. As an example, when emitting audio output, source ports typically
+ * are mix ports (audio data from the framework), the sink is a device
+ * port. When acquiring audio, source ports are device ports, the sink is a mix
+ * port.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioRoute {
+    /**
+     * The list of IDs of source audio ports ('AudioPort.id').
+     * There must be at least one source in a valid route and all IDs must be
+     * unique.
+     */
+    int[] sourcePortIds;
+    /** The ID of the sink audio port ('AudioPort.id'). */
+    int sinkPortId;
+    /** If set, only one source can be active, mixing is not supported. */
+    boolean isExclusive;
+}
diff --git a/audio/aidl/android/hardware/audio/core/IConfig.aidl b/audio/aidl/android/hardware/audio/core/IConfig.aidl
new file mode 100644
index 0000000..c7bb414
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IConfig.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+/**
+ * This interface provides system-wide configuration parameters for audio I/O
+ * (by "system" here we mean the device running Android).
+ */
+@VintfStability
+interface IConfig {}
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
new file mode 100644
index 0000000..d47ea3c
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+import android.hardware.audio.common.SinkMetadata;
+import android.hardware.audio.common.SourceMetadata;
+import android.hardware.audio.core.AudioPatch;
+import android.hardware.audio.core.AudioRoute;
+import android.hardware.audio.core.IStreamIn;
+import android.hardware.audio.core.IStreamOut;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.audio.common.AudioPort;
+import android.media.audio.common.AudioPortConfig;
+
+/**
+ * Each instance of IModule corresponds to a separate audio module. The system
+ * (the term "system" as used here applies to the entire device running Android)
+ * may have multiple modules due to the physical architecture, for example, it
+ * can have multiple DSPs or other audio I/O units which are not interconnected
+ * in hardware directly. Usually there is at least one audio module which is
+ * responsible for the "main" (or "built-in") audio functionality of the
+ * system. Even if the system lacks any physical audio I/O capabilities, there
+ * will be a "null" audio module.
+ *
+ * On a typical mobile phone there is usually a main DSP module which handles
+ * most of the phone's audio I/O via the built-in speakers and microphones. USB
+ * audio can exist as a separate module. Some audio modules can be implemented
+ * purely in software, for example, the remote submix module.
+ */
+@VintfStability
+interface IModule {
+    /**
+     * Return all audio patches of this module.
+     *
+     * Returns a list of audio patches, that is, established connections between
+     * audio port configurations.
+     *
+     * @return The list of audio patches.
+     */
+    AudioPatch[] getAudioPatches();
+
+    /**
+     * Return the current state of the audio port.
+     *
+     * Using the port ID provided on input, returns the current state of the
+     * audio port. For device port representing a connection to some external
+     * device, e.g. over HDMI or USB, currently supported audio profiles and
+     * extra audio descriptors may change.
+     *
+     * For all other audio ports it must be the same configuration as returned
+     * for this port ID by 'getAudioPorts'.
+     *
+     * @return The current state of an audio port.
+     * @param portId The ID of the audio port.
+     * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
+     */
+    AudioPort getAudioPort(int portId);
+
+    /**
+     * Return all active audio port configurations of this module.
+     *
+     * Returns a list of active configurations that are currently set for mix
+     * ports and device ports. Each returned configuration must have an unique
+     * ID within this module ('AudioPortConfig.id' field), which can coincide
+     * with an ID of an audio port, if the port only supports a single active
+     * configuration. Each returned configuration must also have a reference to
+     * an existing port ('AudioPortConfig.portId' field). All optional
+     * (nullable) fields of the configurations must be initialized by the HAL
+     * module.
+     *
+     * @return The list of active audio port configurations.
+     */
+    AudioPortConfig[] getAudioPortConfigs();
+
+    /**
+     * Return all audio ports provided by this module.
+     *
+     * Returns a list of all mix ports and device ports provided by this
+     * module. Each returned port must have a unique ID within this module
+     * ('AudioPort.id' field). The returned list must not change during
+     * the lifetime of the IModule instance. For audio ports with dynamic
+     * profiles (changing depending on external devices being connected
+     * to the system) an empty list of profiles must be returned. The list
+     * of currently supported audio profiles is obtained from 'getAudioPort'
+     * method.
+     *
+     * @return The list of audio ports.
+     */
+    AudioPort[] getAudioPorts();
+
+    /**
+     * Return all audio routes of this module.
+     *
+     * Returns a list of audio routes, that is, allowed connections between
+     * audio ports. The returned list must not change during the lifetime of the
+     * IModule instance.
+     *
+     * @return The list of audio routes.
+     */
+    AudioRoute[] getAudioRoutes();
+
+    /**
+     * Open an input stream using an existing audio mix port configuration.
+     *
+     * The audio port configuration ID must be obtained by calling
+     * 'setAudioPortConfig' method. Existence of an audio patch involving this
+     * port configuration is not required for successful opening of a stream.
+     *
+     * Only one stream is allowed per audio port configuration. HAL module can
+     * also set a limit on how many output streams can be opened for a particular
+     * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field.
+     *
+     * @return An opened input stream.
+     * @param portConfigId The ID of the audio mix port config.
+     * @param sinkMetadata Description of the audio that will be recorded.
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If the port config can not be found by the ID.
+     *                             - If the port config is not of an input mix port.
+     * @throws EX_ILLEGAL_STATE In the following cases:
+     *                          - If the port config already has a stream opened on it.
+     *                          - If the limit on the open stream count for the port has
+     *                            been reached.
+     */
+    IStreamIn openInputStream(int portConfigId, in SinkMetadata sinkMetadata);
+
+    /**
+     * Open an output stream using an existing audio mix port configuration.
+     *
+     * The audio port configuration ID must be obtained by calling
+     * 'setAudioPortConfig' method. Existence of an audio patch involving this
+     * port configuration is not required for successful opening of a stream.
+     *
+     * If the port configuration has 'COMPRESS_OFFLOAD' output flag set,
+     * the framework must provide additional information about the encoded
+     * audio stream in 'offloadInfo' argument.
+     *
+     * Only one stream is allowed per audio port configuration. HAL module can
+     * also set a limit on how many output streams can be opened for a particular
+     * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field.
+     * Only one stream can be opened on the audio port with 'PRIMARY' output
+     * flag. This rule can not be overridden with 'maxOpenStreamCount' field.
+     *
+     * @return An opened output stream.
+     * @param portConfigId The ID of the audio mix port config.
+     * @param sourceMetadata Description of the audio that will be played.
+     * @param offloadInfo Additional information for offloaded playback.
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If the port config can not be found by the ID.
+     *                             - If the port config is not of an output mix port.
+     * @throws EX_ILLEGAL_STATE In the following cases:
+     *                          - If the port config already has a stream opened on it.
+     *                          - If the limit on the open stream count for the port has
+     *                            been reached.
+     *                          - If another opened stream already exists for the 'PRIMARY'
+     *                            output port.
+     */
+    IStreamOut openOutputStream(int portConfigId, in SourceMetadata sourceMetadata,
+            in @nullable AudioOffloadInfo offloadInfo);
+
+    /**
+     * Set an audio patch.
+     *
+     * This method creates new or updates an existing audio patch. If the
+     * requested audio patch does not have a specified id, then a new patch is
+     * created and an ID is allocated for it by the HAL module. Otherwise an
+     * attempt to update an existing patch is made. It is recommended that
+     * updating of an existing audio patch should be performed by the HAL module
+     * in a way that does not interrupt active audio streams involving audio
+     * port configurations of the patch. If the HAL module is unable to avoid
+     * interruption when updating a certain patch, it is permitted to allocate a
+     * new patch ID for the result. The returned audio patch contains all the
+     * information about the new or updated audio patch.
+     *
+     * Audio port configurations specified in the patch must be obtained by
+     * calling 'setAudioPortConfig' method. There must be an audio route which
+     * allows connection between the audio ports whose configurations are used.
+     * An audio patch may be created before or after an audio steam is created
+     * for this configuration.
+     *
+     * @return Resulting audio patch.
+     * @param requested Requested audio patch.
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If the patch is invalid (see AudioPatch).
+     *                             - If a port config can not be found from the specified IDs.
+     *                             - If there are no routes satisfying the patch.
+     *                             - If an existing patch can not be found by the ID.
+     * @throws EX_ILLEGAL_STATE In the following cases:
+     *                          - If application of the patch can only use a route with an
+     *                            exclusive use the sink port, and it is already patched.
+     * @throws EX_UNSUPPORTED_OPERATION If the patch can not be established because
+     *                                  the HAL module does not support this otherwise valid
+     *                                  patch configuration. For example, if it's a patch
+     *                                  between multiple sources and sinks, and the HAL module
+     *                                  does not support this.
+     */
+    AudioPatch setAudioPatch(in AudioPatch requested);
+
+    /**
+     * Set the active configuration of an audio port.
+     *
+     * This method is used to create or update an active configuration for a mix
+     * port or a device port. The port is specified using the
+     * 'AudioPortConfig.portId' field. If the requested audio port
+     * configuration does not have a specified id in the 'AudioPortConfig.id'
+     * field, then a new configuration is created and an ID is allocated for it
+     * by the HAL module. Otherwise an attempt to update an existing port
+     * configuration is made. The HAL module returns the resulting audio port
+     * configuration. Depending on the port and on the capabilities of the HAL
+     * module, it can either update an existing port configuration (same port
+     * configuration ID remains), or create a new one. The resulting port
+     * configuration ID is returned in the 'id' field of the 'suggested'
+     * argument.
+     *
+     * If the specified port configuration can not be set, this method must
+     * return 'false' and provide its own suggestion in the output
+     * parameter. The framework can then set the suggested configuration on a
+     * subsequent retry call to this method.
+     *
+     * @return Whether the requested configuration has been applied.
+     * @param requested Requested audio port configuration.
+     * @param suggested Same as requested configuration, if it was applied.
+     *                  Suggested audio port configuration if the requested
+     *                  configuration can't be applied.
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If neither port config ID, nor port ID are specified.
+     *                             - If an existing port config can not be found by the ID.
+     *                             - If the port can not be found by the port ID.
+     *                             - If it is not possible to generate a suggested port
+     *                               configuration, for example, if the port only has dynamic
+     *                               profiles and they are currently empty.
+     */
+    boolean setAudioPortConfig(in AudioPortConfig requested, out AudioPortConfig suggested);
+
+    /**
+     * Reset the audio patch.
+     *
+     * Resets previously created audio patch using its ID ('AudioPatch.id').  It
+     * is allowed to reset a patch which uses audio port configurations having
+     * associated streams. In this case the mix port becomes disconnected from
+     * the hardware, but the stream does not close.
+     *
+     * @param patchId The ID of the audio patch.
+     * @throws EX_ILLEGAL_ARGUMENT If an existing patch can not be found by the ID.
+     */
+    void resetAudioPatch(int patchId);
+
+    /**
+     * Reset the audio port configuration.
+     *
+     * Resets the specified audio port configuration, discarding all changes
+     * previously done by the framework. That means, if a call to this method is
+     * a success, the effect of all previous calls to 'setAudioPortConfig' which
+     * used or initially have generated the provided 'portConfigId', since the
+     * module start, or since the last call to this method, has been canceled.
+     *
+     * Audio port configurations of mix ports with streams opened on them can
+     * not be reset. Also can not be reset port configurations currently used by
+     * any patches.
+     *
+     * @param portConfigId The ID of the audio port config.
+     * @throws EX_ILLEGAL_ARGUMENT If the port config can not be found by the ID.
+     * @throws EX_ILLEGAL_STATE In the following cases:
+     *                          - If the port config has a stream opened on it;
+     *                          - If the port config is used by a patch.
+     */
+    void resetAudioPortConfig(int portConfigId);
+}
diff --git a/audio/aidl/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl
new file mode 100644
index 0000000..b770449
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+import android.hardware.audio.common.SinkMetadata;
+
+/**
+ * This interface provides means for receiving audio data from input devices.
+ */
+@VintfStability
+interface IStreamIn {
+    /**
+     * Close the stream.
+     *
+     * Releases any resources allocated for this stream on the HAL module side.
+     * The stream can not be operated after it has been closed. Methods of this
+     * interface throw EX_ILLEGAL_STATE in for a closed stream.
+     *
+     * @throws EX_ILLEGAL_STATE If the stream has already been closed.
+     */
+    void close();
+
+    /**
+     * Update stream metadata.
+     *
+     * Updates the metadata initially provided at the stream creation.
+     *
+     * @param sinkMetadata Updated metadata.
+     * @throws EX_ILLEGAL_STATE If the stream is closed.
+     */
+    void updateMetadata(in SinkMetadata sinkMetadata);
+}
diff --git a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
new file mode 100644
index 0000000..60212fc
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.hardware.audio.core;
+
+import android.hardware.audio.common.SourceMetadata;
+
+/**
+ * This interface provides means for sending audio data to output devices.
+ */
+@VintfStability
+interface IStreamOut {
+    /**
+     * Close the stream.
+     *
+     * Releases any resources allocated for this stream on the HAL module side.
+     * The stream can not be operated after it has been closed. Methods of this
+     * interface throw EX_ILLEGAL_STATE in for a closed stream.
+     *
+     * @throws EX_ILLEGAL_STATE If the stream has already been closed.
+     */
+    void close();
+
+    /**
+     * Update stream metadata.
+     *
+     * Updates the metadata initially provided at the stream creation.
+     *
+     * @param sourceMetadata Updated metadata.
+     * @throws EX_ILLEGAL_STATE If the stream is closed.
+     */
+    void updateMetadata(in SourceMetadata sourceMetadata);
+}
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
new file mode 100644
index 0000000..0a6fe60
--- /dev/null
+++ b/audio/aidl/default/Android.bp
@@ -0,0 +1,45 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library_static {
+    name: "libaudioserviceexampleimpl",
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.audio.core-V1-ndk",
+    ],
+    export_include_dirs: ["include"],
+    srcs: [
+        "Config.cpp",
+        "Configuration.cpp",
+        "Module.cpp",
+        "Stream.cpp",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_binary {
+    name: "android.hardware.audio.service-aidl.example",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.audio.service-aidl.example.rc"],
+    vintf_fragments: ["android.hardware.audio.service-aidl.xml"],
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.audio.core-V1-ndk",
+    ],
+    static_libs: [
+        "libaudioserviceexampleimpl",
+    ],
+    srcs: ["main.cpp"],
+}
diff --git a/audio/aidl/default/Config.cpp b/audio/aidl/default/Config.cpp
new file mode 100644
index 0000000..3f7a3d3
--- /dev/null
+++ b/audio/aidl/default/Config.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "core-impl/Config.h"
+
+namespace aidl::android::hardware::audio::core {}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
new file mode 100644
index 0000000..1104caa
--- /dev/null
+++ b/audio/aidl/default/Configuration.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <aidl/android/media/audio/common/AudioChannelLayout.h>
+#include <aidl/android/media/audio/common/AudioDeviceType.h>
+#include <aidl/android/media/audio/common/AudioFormatType.h>
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+
+#include "aidl/android/media/audio/common/AudioFormatDescription.h"
+#include "core-impl/Configuration.h"
+
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioGainConfig;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortDeviceExt;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioPortMixExt;
+using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Int;
+using aidl::android::media::audio::common::PcmType;
+
+namespace aidl::android::hardware::audio::core::internal {
+
+static AudioProfile createProfile(PcmType pcmType, const std::vector<int32_t>& channelLayouts,
+                                  const std::vector<int32_t>& sampleRates) {
+    AudioProfile profile;
+    profile.format.type = AudioFormatType::PCM;
+    profile.format.pcm = pcmType;
+    for (auto layout : channelLayouts) {
+        profile.channelMasks.push_back(
+                AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout));
+    }
+    profile.sampleRates.insert(profile.sampleRates.end(), sampleRates.begin(), sampleRates.end());
+    return profile;
+}
+
+static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags) {
+    AudioPortDeviceExt deviceExt;
+    deviceExt.device.type.type = devType;
+    deviceExt.flags = flags;
+    return AudioPortExt::make<AudioPortExt::Tag::device>(deviceExt);
+}
+
+static AudioPortExt createPortMixExt(int32_t maxOpenStreamCount, int32_t maxActiveStreamCount) {
+    AudioPortMixExt mixExt;
+    mixExt.maxOpenStreamCount = maxOpenStreamCount;
+    mixExt.maxActiveStreamCount = maxActiveStreamCount;
+    return AudioPortExt::make<AudioPortExt::Tag::mix>(mixExt);
+}
+
+static AudioPort createPort(int32_t id, const std::string& name, int32_t flags, bool isInput,
+                            const AudioPortExt& ext) {
+    AudioPort port;
+    port.id = id;
+    port.name = name;
+    port.flags = isInput ? AudioIoFlags::make<AudioIoFlags::Tag::input>(flags)
+                         : AudioIoFlags::make<AudioIoFlags::Tag::output>(flags);
+    port.ext = ext;
+    return port;
+}
+
+static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout,
+                                        int32_t sampleRate, int32_t flags, bool isInput,
+                                        const AudioPortExt& ext) {
+    AudioPortConfig config;
+    config.id = id;
+    config.portId = portId;
+    config.sampleRate = Int{.value = sampleRate};
+    config.channelMask = AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout);
+    config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
+    config.gain = AudioGainConfig();
+    config.flags = isInput ? AudioIoFlags::make<AudioIoFlags::Tag::input>(flags)
+                           : AudioIoFlags::make<AudioIoFlags::Tag::output>(flags);
+    config.ext = ext;
+    return config;
+}
+
+static AudioRoute createRoute(const std::vector<int32_t>& sources, int32_t sink) {
+    AudioRoute route;
+    route.sinkPortId = sink;
+    route.sourcePortIds.insert(route.sourcePortIds.end(), sources.begin(), sources.end());
+    return route;
+}
+
+Configuration& getNullPrimaryConfiguration() {
+    static Configuration configuration = []() {
+        Configuration c;
+
+        AudioPort nullOutDevice =
+                createPort(c.nextPortId++, "Null", 0, false,
+                           createDeviceExt(AudioDeviceType::OUT_SPEAKER,
+                                           1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+        c.ports.push_back(nullOutDevice);
+
+        AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
+                                             1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
+                                             false, createPortMixExt(1, 1));
+        primaryOutMix.profiles.push_back(
+                createProfile(PcmType::INT_16_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+                              {44100, 48000}));
+        primaryOutMix.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+                              {44100, 48000}));
+        c.ports.push_back(primaryOutMix);
+
+        c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id));
+
+        c.initialConfigs.push_back(
+                createPortConfig(nullOutDevice.id, nullOutDevice.id, PcmType::INT_24_BIT,
+                                 AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false,
+                                 createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0)));
+
+        AudioPort loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false,
+                                             createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0));
+        loopOutDevice.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+        c.ports.push_back(loopOutDevice);
+
+        AudioPort loopOutMix =
+                createPort(c.nextPortId++, "loopback output", 0, false, createPortMixExt(0, 0));
+        loopOutMix.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+        c.ports.push_back(loopOutMix);
+
+        c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+
+        AudioPort zeroInDevice =
+                createPort(c.nextPortId++, "Zero", 0, true,
+                           createDeviceExt(AudioDeviceType::IN_MICROPHONE,
+                                           1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+        c.ports.push_back(zeroInDevice);
+
+        AudioPort primaryInMix =
+                createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2));
+        primaryInMix.profiles.push_back(
+                createProfile(PcmType::INT_16_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
+                               AudioChannelLayout::LAYOUT_FRONT_BACK},
+                              {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}));
+        primaryInMix.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
+                               AudioChannelLayout::LAYOUT_FRONT_BACK},
+                              {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}));
+        c.ports.push_back(primaryInMix);
+
+        c.routes.push_back(createRoute({zeroInDevice.id}, primaryInMix.id));
+
+        c.initialConfigs.push_back(
+                createPortConfig(zeroInDevice.id, zeroInDevice.id, PcmType::INT_24_BIT,
+                                 AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
+                                 createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
+
+        AudioPort loopInDevice = createPort(c.nextPortId++, "Loopback In", 0, true,
+                                            createDeviceExt(AudioDeviceType::IN_SUBMIX, 0));
+        loopInDevice.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+        c.ports.push_back(loopInDevice);
+
+        AudioPort loopInMix =
+                createPort(c.nextPortId++, "loopback input", 0, true, createPortMixExt(0, 0));
+        loopInMix.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+        c.ports.push_back(loopInMix);
+
+        c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id));
+
+        c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
+        return c;
+    }();
+    return configuration;
+}
+
+}  // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
new file mode 100644
index 0000000..e0a68a5
--- /dev/null
+++ b/audio/aidl/default/Module.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <algorithm>
+#include <set>
+
+#define LOG_TAG "AHAL_Module"
+#define LOG_NDEBUG 0
+#include <android-base/logging.h>
+
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+
+#include "core-impl/Module.h"
+#include "core-impl/utils.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Int;
+
+namespace aidl::android::hardware::audio::core {
+
+namespace {
+
+bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) {
+    *config = {};
+    config->portId = port.id;
+    if (port.profiles.empty()) {
+        LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles";
+        return false;
+    }
+    const auto& profile = port.profiles.begin();
+    config->format = profile->format;
+    if (profile->channelMasks.empty()) {
+        LOG(ERROR) << __func__ << ": the first profile in port " << port.id
+                   << " has no channel masks";
+        return false;
+    }
+    config->channelMask = *profile->channelMasks.begin();
+    if (profile->sampleRates.empty()) {
+        LOG(ERROR) << __func__ << ": the first profile in port " << port.id
+                   << " has no sample rates";
+        return false;
+    }
+    Int sampleRate;
+    sampleRate.value = *profile->sampleRates.begin();
+    config->sampleRate = sampleRate;
+    config->flags = port.flags;
+    config->ext = port.ext;
+    return true;
+}
+
+bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format,
+                      AudioProfile* profile) {
+    if (auto profilesIt =
+                find_if(port.profiles.begin(), port.profiles.end(),
+                        [&format](const auto& profile) { return profile.format == format; });
+        profilesIt != port.profiles.end()) {
+        *profile = *profilesIt;
+        return true;
+    }
+    return false;
+}
+}  // namespace
+
+void Module::cleanUpPatch(int32_t patchId) {
+    erase_all_values(mPatches, std::set<int32_t>{patchId});
+}
+
+void Module::cleanUpPatches(int32_t portConfigId) {
+    auto& patches = getConfig().patches;
+    if (patches.size() == 0) return;
+    auto range = mPatches.equal_range(portConfigId);
+    for (auto it = range.first; it != range.second; ++it) {
+        auto patchIt = findById<AudioPatch>(patches, it->second);
+        if (patchIt != patches.end()) {
+            erase_if(patchIt->sourcePortConfigIds,
+                     [portConfigId](auto e) { return e == portConfigId; });
+            erase_if(patchIt->sinkPortConfigIds,
+                     [portConfigId](auto e) { return e == portConfigId; });
+        }
+    }
+    std::set<int32_t> erasedPatches;
+    for (size_t i = patches.size() - 1; i != 0; --i) {
+        const auto& patch = patches[i];
+        if (patch.sourcePortConfigIds.empty() || patch.sinkPortConfigIds.empty()) {
+            erasedPatches.insert(patch.id);
+            patches.erase(patches.begin() + i);
+        }
+    }
+    erase_all_values(mPatches, erasedPatches);
+}
+
+internal::Configuration& Module::getConfig() {
+    if (!mConfig) {
+        mConfig.reset(new internal::Configuration(internal::getNullPrimaryConfiguration()));
+    }
+    return *mConfig;
+}
+
+void Module::registerPatch(const AudioPatch& patch) {
+    auto& configs = getConfig().portConfigs;
+    auto do_insert = [&](const std::vector<int32_t>& portConfigIds) {
+        for (auto portConfigId : portConfigIds) {
+            auto configIt = findById<AudioPortConfig>(configs, portConfigId);
+            if (configIt != configs.end()) {
+                mPatches.insert(std::pair{portConfigId, patch.id});
+                if (configIt->portId != portConfigId) {
+                    mPatches.insert(std::pair{configIt->portId, patch.id});
+                }
+            }
+        };
+    };
+    do_insert(patch.sourcePortConfigIds);
+    do_insert(patch.sinkPortConfigIds);
+}
+
+ndk::ScopedAStatus Module::getAudioPatches(std::vector<AudioPatch>* _aidl_return) {
+    *_aidl_return = getConfig().patches;
+    LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches";
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioPort(int32_t in_portId, AudioPort* _aidl_return) {
+    auto& ports = getConfig().ports;
+    auto portIt = findById<AudioPort>(ports, in_portId);
+    if (portIt != ports.end()) {
+        *_aidl_return = *portIt;
+        LOG(DEBUG) << __func__ << ": returning port by id " << in_portId;
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+ndk::ScopedAStatus Module::getAudioPortConfigs(std::vector<AudioPortConfig>* _aidl_return) {
+    *_aidl_return = getConfig().portConfigs;
+    LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " port configs";
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioPorts(std::vector<AudioPort>* _aidl_return) {
+    *_aidl_return = getConfig().ports;
+    LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " ports";
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioRoutes(std::vector<AudioRoute>* _aidl_return) {
+    *_aidl_return = getConfig().routes;
+    LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " routes";
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::openInputStream(int32_t in_portConfigId,
+                                           const SinkMetadata& in_sinkMetadata,
+                                           std::shared_ptr<IStreamIn>* _aidl_return) {
+    auto& configs = getConfig().portConfigs;
+    auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
+    if (portConfigIt == configs.end()) {
+        LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    const int32_t portId = portConfigIt->portId;
+    // In our implementation, configs of mix ports always have unique IDs.
+    CHECK(portId != in_portConfigId);
+    auto& ports = getConfig().ports;
+    auto portIt = findById<AudioPort>(ports, portId);
+    if (portIt == ports.end()) {
+        LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id "
+                   << in_portConfigId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (portIt->flags.getTag() != AudioIoFlags::Tag::input ||
+        portIt->ext.getTag() != AudioPortExt::Tag::mix) {
+        LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                   << " does not correspond to an input mix port";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (mStreams.count(in_portConfigId) != 0) {
+        LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                   << " already has a stream opened on it";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
+    if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) {
+        LOG(ERROR) << __func__ << ": port id " << portId
+                   << " has already reached maximum allowed opened stream count: "
+                   << maxOpenStreamCount;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    auto stream = ndk::SharedRefBase::make<StreamIn>(in_sinkMetadata);
+    mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
+    *_aidl_return = std::move(stream);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::openOutputStream(int32_t in_portConfigId,
+                                            const SourceMetadata& in_sourceMetadata,
+                                            const std::optional<AudioOffloadInfo>& in_offloadInfo,
+                                            std::shared_ptr<IStreamOut>* _aidl_return) {
+    auto& configs = getConfig().portConfigs;
+    auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
+    if (portConfigIt == configs.end()) {
+        LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    const int32_t portId = portConfigIt->portId;
+    // In our implementation, configs of mix ports always have unique IDs.
+    CHECK(portId != in_portConfigId);
+    auto& ports = getConfig().ports;
+    auto portIt = findById<AudioPort>(ports, portId);
+    if (portIt == ports.end()) {
+        LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id "
+                   << in_portConfigId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (portIt->flags.getTag() != AudioIoFlags::Tag::output ||
+        portIt->ext.getTag() != AudioPortExt::Tag::mix) {
+        LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                   << " does not correspond to an output mix port";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (mStreams.count(in_portConfigId) != 0) {
+        LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                   << " already has a stream opened on it";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
+    if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) {
+        LOG(ERROR) << __func__ << ": port id " << portId
+                   << " has already reached maximum allowed opened stream count: "
+                   << maxOpenStreamCount;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    auto stream = ndk::SharedRefBase::make<StreamOut>(in_sourceMetadata, in_offloadInfo);
+    mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
+    *_aidl_return = std::move(stream);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPatch* _aidl_return) {
+    if (in_requested.sourcePortConfigIds.empty()) {
+        LOG(ERROR) << __func__ << ": requested patch has empty sources list";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (!all_unique<int32_t>(in_requested.sourcePortConfigIds)) {
+        LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sources list";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (in_requested.sinkPortConfigIds.empty()) {
+        LOG(ERROR) << __func__ << ": requested patch has empty sinks list";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (!all_unique<int32_t>(in_requested.sinkPortConfigIds)) {
+        LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sinks list";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    auto& configs = getConfig().portConfigs;
+    std::vector<int32_t> missingIds;
+    auto sources =
+            selectByIds<AudioPortConfig>(configs, in_requested.sourcePortConfigIds, &missingIds);
+    if (!missingIds.empty()) {
+        LOG(ERROR) << __func__ << ": following source port config ids not found: "
+                   << ::android::internal::ToString(missingIds);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    auto sinks = selectByIds<AudioPortConfig>(configs, in_requested.sinkPortConfigIds, &missingIds);
+    if (!missingIds.empty()) {
+        LOG(ERROR) << __func__ << ": following sink port config ids not found: "
+                   << ::android::internal::ToString(missingIds);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    // bool indicates whether a non-exclusive route is available.
+    // If only an exclusive route is available, that means the patch can not be
+    // established if there is any other patch which currently uses the sink port.
+    std::map<int32_t, bool> allowedSinkPorts;
+    auto& routes = getConfig().routes;
+    for (auto src : sources) {
+        for (const auto& r : routes) {
+            const auto& srcs = r.sourcePortIds;
+            if (std::find(srcs.begin(), srcs.end(), src->portId) != srcs.end()) {
+                if (!allowedSinkPorts[r.sinkPortId]) {  // prefer non-exclusive
+                    allowedSinkPorts[r.sinkPortId] = !r.isExclusive;
+                }
+            }
+        }
+    }
+    for (auto sink : sinks) {
+        if (allowedSinkPorts.count(sink->portId) == 0) {
+            LOG(ERROR) << __func__ << ": there is no route to the sink port id " << sink->portId;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+
+    auto& patches = getConfig().patches;
+    auto existing = patches.end();
+    std::optional<decltype(mPatches)> patchesBackup;
+    if (in_requested.id != 0) {
+        existing = findById<AudioPatch>(patches, in_requested.id);
+        if (existing != patches.end()) {
+            patchesBackup = mPatches;
+            cleanUpPatch(existing->id);
+        } else {
+            LOG(ERROR) << __func__ << ": not found existing patch id " << in_requested.id;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+    // Validate the requested patch.
+    for (const auto& [sinkPortId, nonExclusive] : allowedSinkPorts) {
+        if (!nonExclusive && mPatches.count(sinkPortId) != 0) {
+            LOG(ERROR) << __func__ << ": sink port id " << sinkPortId
+                       << "is exclusive and is already used by some other patch";
+            if (patchesBackup.has_value()) {
+                mPatches = std::move(*patchesBackup);
+            }
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
+    }
+    *_aidl_return = in_requested;
+    if (existing == patches.end()) {
+        _aidl_return->id = getConfig().nextPatchId++;
+        patches.push_back(*_aidl_return);
+        existing = patches.begin() + (patches.size() - 1);
+    } else {
+        *existing = *_aidl_return;
+    }
+    registerPatch(*existing);
+    LOG(DEBUG) << __func__ << ": created or updated patch id " << _aidl_return->id;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requested,
+                                              AudioPortConfig* out_suggested, bool* _aidl_return) {
+    LOG(DEBUG) << __func__ << ": requested " << in_requested.toString();
+    auto& configs = getConfig().portConfigs;
+    auto existing = configs.end();
+    if (in_requested.id != 0) {
+        if (existing = findById<AudioPortConfig>(configs, in_requested.id);
+            existing == configs.end()) {
+            LOG(ERROR) << __func__ << ": existing port config id " << in_requested.id
+                       << " not found";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+
+    const int portId = existing != configs.end() ? existing->portId : in_requested.portId;
+    if (portId == 0) {
+        LOG(ERROR) << __func__ << ": input port config does not specify portId";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    auto& ports = getConfig().ports;
+    auto portIt = findById<AudioPort>(ports, portId);
+    if (portIt == ports.end()) {
+        LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (existing != configs.end()) {
+        *out_suggested = *existing;
+    } else {
+        AudioPortConfig newConfig;
+        if (generateDefaultPortConfig(*portIt, &newConfig)) {
+            *out_suggested = newConfig;
+        } else {
+            LOG(ERROR) << __func__ << ": unable generate a default config for port " << portId;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+    // From this moment, 'out_suggested' is either an existing port config,
+    // or a new generated config. Now attempt to update it according to the specified
+    // fields of 'in_requested'.
+
+    bool requestedIsValid = true, requestedIsFullySpecified = true;
+
+    AudioIoFlags portFlags = portIt->flags;
+    if (in_requested.flags.has_value()) {
+        if (in_requested.flags.value() != portFlags) {
+            LOG(WARNING) << __func__ << ": requested flags "
+                         << in_requested.flags.value().toString() << " do not match port's "
+                         << portId << " flags " << portFlags.toString();
+            requestedIsValid = false;
+        }
+    } else {
+        requestedIsFullySpecified = false;
+    }
+
+    AudioProfile portProfile;
+    if (in_requested.format.has_value()) {
+        const auto& format = in_requested.format.value();
+        if (findAudioProfile(*portIt, format, &portProfile)) {
+            out_suggested->format = format;
+        } else {
+            LOG(WARNING) << __func__ << ": requested format " << format.toString()
+                         << " is not found in port's " << portId << " profiles";
+            requestedIsValid = false;
+        }
+    } else {
+        requestedIsFullySpecified = false;
+    }
+    if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) {
+        LOG(ERROR) << __func__ << ": port " << portId << " does not support format "
+                   << out_suggested->format.value().toString() << " anymore";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    if (in_requested.channelMask.has_value()) {
+        const auto& channelMask = in_requested.channelMask.value();
+        if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) !=
+            portProfile.channelMasks.end()) {
+            out_suggested->channelMask = channelMask;
+        } else {
+            LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString()
+                         << " is not supported for the format " << portProfile.format.toString()
+                         << " by the port " << portId;
+            requestedIsValid = false;
+        }
+    } else {
+        requestedIsFullySpecified = false;
+    }
+
+    if (in_requested.sampleRate.has_value()) {
+        const auto& sampleRate = in_requested.sampleRate.value();
+        if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(),
+                 sampleRate.value) != portProfile.sampleRates.end()) {
+            out_suggested->sampleRate = sampleRate;
+        } else {
+            LOG(WARNING) << __func__ << ": requested sample rate " << sampleRate.value
+                         << " is not supported for the format " << portProfile.format.toString()
+                         << " by the port " << portId;
+            requestedIsValid = false;
+        }
+    } else {
+        requestedIsFullySpecified = false;
+    }
+
+    if (in_requested.gain.has_value()) {
+        // Let's pretend that gain can always be applied.
+        out_suggested->gain = in_requested.gain.value();
+    }
+
+    if (existing == configs.end() && requestedIsValid && requestedIsFullySpecified) {
+        out_suggested->id = getConfig().nextPortId++;
+        configs.push_back(*out_suggested);
+        *_aidl_return = true;
+        LOG(DEBUG) << __func__ << ": created new port config " << out_suggested->toString();
+    } else if (existing != configs.end() && requestedIsValid) {
+        *existing = *out_suggested;
+        *_aidl_return = true;
+        LOG(DEBUG) << __func__ << ": updated port config " << out_suggested->toString();
+    } else {
+        LOG(DEBUG) << __func__ << ": not applied; existing config ? " << (existing != configs.end())
+                   << "; requested is valid? " << requestedIsValid << ", fully specified? "
+                   << requestedIsFullySpecified;
+        *_aidl_return = false;
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
+    auto& patches = getConfig().patches;
+    auto patchIt = findById<AudioPatch>(patches, in_patchId);
+    if (patchIt != patches.end()) {
+        cleanUpPatch(patchIt->id);
+        patches.erase(patchIt);
+        LOG(DEBUG) << __func__ << ": erased patch " << in_patchId;
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": patch id " << in_patchId << " not found";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) {
+    auto& configs = getConfig().portConfigs;
+    auto configIt = findById<AudioPortConfig>(configs, in_portConfigId);
+    if (configIt != configs.end()) {
+        if (mStreams.count(in_portConfigId) != 0) {
+            LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                       << " has a stream opened on it";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
+        auto patchIt = mPatches.find(in_portConfigId);
+        if (patchIt != mPatches.end()) {
+            LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                       << " is used by the patch with id " << patchIt->second;
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
+        auto& initials = getConfig().initialConfigs;
+        auto initialIt = findById<AudioPortConfig>(initials, in_portConfigId);
+        if (initialIt == initials.end()) {
+            configs.erase(configIt);
+            LOG(DEBUG) << __func__ << ": erased port config " << in_portConfigId;
+        } else if (*configIt != *initialIt) {
+            *configIt = *initialIt;
+            LOG(DEBUG) << __func__ << ": reset port config " << in_portConfigId;
+        }
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": port config id " << in_portConfigId << " not found";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
new file mode 100644
index 0000000..e16b2c6
--- /dev/null
+++ b/audio/aidl/default/Stream.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#define LOG_TAG "AHAL_Stream"
+#define LOG_NDEBUG 0
+#include <android-base/logging.h>
+
+#include "core-impl/Stream.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+
+namespace aidl::android::hardware::audio::core {
+
+StreamIn::StreamIn(const SinkMetadata& sinkMetadata) : mMetadata(sinkMetadata) {}
+
+ndk::ScopedAStatus StreamIn::close() {
+    LOG(DEBUG) << __func__;
+    if (!mIsClosed) {
+        mIsClosed = true;
+        return ndk::ScopedAStatus::ok();
+    } else {
+        LOG(ERROR) << __func__ << ": stream was already closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+}
+
+ndk::ScopedAStatus StreamIn::updateMetadata(const SinkMetadata& in_sinkMetadata) {
+    LOG(DEBUG) << __func__;
+    if (!mIsClosed) {
+        mMetadata = in_sinkMetadata;
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": stream was closed";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+StreamOut::StreamOut(const SourceMetadata& sourceMetadata,
+                     const std::optional<AudioOffloadInfo>& offloadInfo)
+    : mMetadata(sourceMetadata), mOffloadInfo(offloadInfo) {}
+
+ndk::ScopedAStatus StreamOut::close() {
+    LOG(DEBUG) << __func__;
+    if (!mIsClosed) {
+        mIsClosed = true;
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": stream was already closed";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+ndk::ScopedAStatus StreamOut::updateMetadata(const SourceMetadata& in_sourceMetadata) {
+    LOG(DEBUG) << __func__;
+    if (!mIsClosed) {
+        mMetadata = in_sourceMetadata;
+        return ndk::ScopedAStatus::ok();
+    }
+    LOG(ERROR) << __func__ << ": stream was closed";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.example.rc b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc
new file mode 100644
index 0000000..02a9c37
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc
@@ -0,0 +1,9 @@
+service vendor.audio-hal-aidl /vendor/bin/hw/android.hardware.audio.service-aidl.example
+    class hal
+    user audioserver
+    # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
+    group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
+    capabilities BLOCK_SUSPEND
+    ioprio rt 4
+    task_profiles ProcessCapacityHigh HighPerformance
+    onrestart restart audioserver
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.xml b/audio/aidl/default/android.hardware.audio.service-aidl.xml
new file mode 100644
index 0000000..bb4b01a
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.xml
@@ -0,0 +1,12 @@
+<manifest version="1.0" type="device">
+  <hal format="aidl">
+    <name>android.hardware.audio.core</name>
+    <version>1</version>
+    <fqname>IModule/default</fqname>
+  </hal>
+  <hal format="aidl">
+    <name>android.hardware.audio.core</name>
+    <version>1</version>
+    <fqname>IConfig/default</fqname>
+  </hal>
+</manifest>
diff --git a/audio/aidl/default/include/core-impl/Config.h b/audio/aidl/default/include/core-impl/Config.h
new file mode 100644
index 0000000..b62a14b
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Config.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/audio/core/BnConfig.h>
+
+namespace aidl::android::hardware::audio::core {
+
+class Config : public BnConfig {};
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h
new file mode 100644
index 0000000..17e342d
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <aidl/android/hardware/audio/core/AudioPatch.h>
+#include <aidl/android/hardware/audio/core/AudioRoute.h>
+#include <aidl/android/media/audio/common/AudioPort.h>
+#include <aidl/android/media/audio/common/AudioPortConfig.h>
+
+namespace aidl::android::hardware::audio::core::internal {
+
+struct Configuration {
+    std::vector<::aidl::android::media::audio::common::AudioPort> ports;
+    std::vector<::aidl::android::media::audio::common::AudioPortConfig> portConfigs;
+    std::vector<::aidl::android::media::audio::common::AudioPortConfig> initialConfigs;
+    std::vector<AudioRoute> routes;
+    std::vector<AudioPatch> patches;
+    int32_t nextPortId = 1;
+    int32_t nextPatchId = 1;
+};
+
+Configuration& getNullPrimaryConfiguration();
+
+}  // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
new file mode 100644
index 0000000..359626c
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include <aidl/android/hardware/audio/core/BnModule.h>
+
+#include "core-impl/Configuration.h"
+#include "core-impl/Stream.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class Module : public BnModule {
+    ndk::ScopedAStatus getAudioPatches(std::vector<AudioPatch>* _aidl_return) override;
+    ndk::ScopedAStatus getAudioPort(
+            int32_t in_portId,
+            ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
+    ndk::ScopedAStatus getAudioPortConfigs(
+            std::vector<::aidl::android::media::audio::common::AudioPortConfig>* _aidl_return)
+            override;
+    ndk::ScopedAStatus getAudioPorts(
+            std::vector<::aidl::android::media::audio::common::AudioPort>* _aidl_return) override;
+    ndk::ScopedAStatus getAudioRoutes(std::vector<AudioRoute>* _aidl_return) override;
+    ndk::ScopedAStatus openInputStream(
+            int32_t in_portConfigId,
+            const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata,
+            std::shared_ptr<IStreamIn>* _aidl_return) override;
+    ndk::ScopedAStatus openOutputStream(
+            int32_t in_portConfigId,
+            const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata,
+            const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+                    in_offloadInfo,
+            std::shared_ptr<IStreamOut>* _aidl_return) override;
+    ndk::ScopedAStatus setAudioPatch(const AudioPatch& in_requested,
+                                     AudioPatch* _aidl_return) override;
+    ndk::ScopedAStatus setAudioPortConfig(
+            const ::aidl::android::media::audio::common::AudioPortConfig& in_requested,
+            ::aidl::android::media::audio::common::AudioPortConfig* out_suggested,
+            bool* _aidl_return) override;
+    ndk::ScopedAStatus resetAudioPatch(int32_t in_patchId) override;
+    ndk::ScopedAStatus resetAudioPortConfig(int32_t in_portConfigId) override;
+
+  private:
+    void cleanUpPatch(int32_t patchId);
+    void cleanUpPatches(int32_t portConfigId);
+    internal::Configuration& getConfig();
+    void registerPatch(const AudioPatch& patch);
+
+    std::unique_ptr<internal::Configuration> mConfig;
+    Streams mStreams;
+    // Maps port ids and port config ids to patch ids.
+    // Multimap because both ports and configs can be used by multiple patches.
+    std::multimap<int32_t, int32_t> mPatches;
+};
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
new file mode 100644
index 0000000..87104dd
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <optional>
+#include <variant>
+
+#include <aidl/android/hardware/audio/common/SinkMetadata.h>
+#include <aidl/android/hardware/audio/common/SourceMetadata.h>
+#include <aidl/android/hardware/audio/core/BnStreamIn.h>
+#include <aidl/android/hardware/audio/core/BnStreamOut.h>
+#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
+
+#include "core-impl/utils.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class StreamIn : public BnStreamIn {
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus updateMetadata(
+            const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata) override;
+
+  public:
+    explicit StreamIn(const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata);
+    bool isClosed() const { return mIsClosed; }
+
+  private:
+    ::aidl::android::hardware::audio::common::SinkMetadata mMetadata;
+    bool mIsClosed = false;
+};
+
+class StreamOut : public BnStreamOut {
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus updateMetadata(
+            const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata)
+            override;
+
+  public:
+    StreamOut(const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+              const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+                      offloadInfo);
+    bool isClosed() const { return mIsClosed; }
+
+  private:
+    ::aidl::android::hardware::audio::common::SourceMetadata mMetadata;
+    std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo;
+    bool mIsClosed = false;
+};
+
+class StreamWrapper {
+  public:
+    explicit StreamWrapper(std::shared_ptr<StreamIn> streamIn) : mStream(streamIn) {}
+    explicit StreamWrapper(std::shared_ptr<StreamOut> streamOut) : mStream(streamOut) {}
+    bool isStreamOpen() const {
+        return std::visit(
+                [](auto&& ws) -> bool {
+                    auto s = ws.lock();
+                    return s && !s->isClosed();
+                },
+                mStream);
+    }
+
+  private:
+    std::variant<std::weak_ptr<StreamIn>, std::weak_ptr<StreamOut>> mStream;
+};
+
+class Streams {
+  public:
+    Streams() = default;
+    Streams(const Streams&) = delete;
+    Streams& operator=(const Streams&) = delete;
+    size_t count(int32_t id) {
+        // Streams do not remove themselves from the collection on close.
+        erase_if(mStreams, [](const auto& pair) { return !pair.second.isStreamOpen(); });
+        return mStreams.count(id);
+    }
+    void insert(int32_t portId, int32_t portConfigId, StreamWrapper sw) {
+        mStreams.insert(std::pair{portConfigId, sw});
+        mStreams.insert(std::pair{portId, sw});
+    }
+
+  private:
+    // Maps port ids and port config ids to streams. Multimap because a port
+    // (not port config) can have multiple streams opened on it.
+    std::multimap<int32_t, StreamWrapper> mStreams;
+};
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/utils.h b/audio/aidl/default/include/core-impl/utils.h
new file mode 100644
index 0000000..7101012
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/utils.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <set>
+#include <vector>
+
+namespace aidl::android::hardware::audio::core {
+
+// Return whether all the elements in the vector are unique.
+template <typename T>
+bool all_unique(const std::vector<T>& v) {
+    return std::set<T>(v.begin(), v.end()).size() == v.size();
+}
+
+// Erase all the specified elements from a map.
+template <typename C, typename V>
+auto erase_all(C& c, const V& keys) {
+    auto oldSize = c.size();
+    for (auto& k : keys) {
+        c.erase(k);
+    }
+    return oldSize - c.size();
+}
+
+// Erase all the elements in the map that satisfy the provided predicate.
+template <typename C, typename P>
+auto erase_if(C& c, P pred) {
+    auto oldSize = c.size();
+    for (auto it = c.begin(), last = c.end(); it != last;) {
+        if (pred(*it)) {
+            it = c.erase(it);
+        } else {
+            ++it;
+        }
+    }
+    return oldSize - c.size();
+}
+
+// Erase all the elements in the map that have specified values.
+template <typename C, typename V>
+auto erase_all_values(C& c, const V& values) {
+    return erase_if(c, [values](const auto& pair) { return values.count(pair.second) != 0; });
+}
+
+// Return non-zero count of elements for any of the provided keys.
+template <typename M, typename V>
+size_t count_any(const M& m, const V& keys) {
+    for (auto& k : keys) {
+        if (size_t c = m.count(k); c != 0) return c;
+    }
+    return 0;
+}
+
+// Assuming that M is a map whose values have an 'id' field,
+// find an element with the specified id.
+template <typename M>
+auto findById(M& m, int32_t id) {
+    return std::find_if(m.begin(), m.end(), [&](const auto& p) { return p.second.id == id; });
+}
+
+// Assuming that the vector contains elements with an 'id' field,
+// find an element with the specified id.
+template <typename T>
+auto findById(std::vector<T>& v, int32_t id) {
+    return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
+}
+
+// Return elements from the vector that have specified ids, also
+// optionally return which ids were not found.
+template <typename T>
+std::vector<T*> selectByIds(std::vector<T>& v, const std::vector<int32_t>& ids,
+                            std::vector<int32_t>* missingIds = nullptr) {
+    std::vector<T*> result;
+    std::set<int32_t> idsSet(ids.begin(), ids.end());
+    for (size_t i = 0; i < v.size(); ++i) {
+        T& e = v[i];
+        if (idsSet.count(e.id) != 0) {
+            result.push_back(&v[i]);
+            idsSet.erase(e.id);
+        }
+    }
+    if (missingIds) {
+        *missingIds = std::vector(idsSet.begin(), idsSet.end());
+    }
+    return result;
+}
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/main.cpp b/audio/aidl/default/main.cpp
new file mode 100644
index 0000000..0de6047
--- /dev/null
+++ b/audio/aidl/default/main.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "core-impl/Config.h"
+#include "core-impl/Module.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::audio::core::Config;
+using aidl::android::hardware::audio::core::Module;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(16);
+
+    // make the default config service
+    auto config = ndk::SharedRefBase::make<Config>();
+    const std::string configName = std::string() + Config::descriptor + "/default";
+    binder_status_t status =
+            AServiceManager_addService(config->asBinder().get(), configName.c_str());
+    CHECK(status == STATUS_OK);
+
+    // make the default module
+    auto moduleDefault = ndk::SharedRefBase::make<Module>();
+    const std::string moduleDefaultName = std::string() + Module::descriptor + "/default";
+    status = AServiceManager_addService(moduleDefault->asBinder().get(), moduleDefaultName.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
new file mode 100644
index 0000000..c160d1f
--- /dev/null
+++ b/audio/aidl/vts/Android.bp
@@ -0,0 +1,32 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsHalAudioCoreTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "ModuleConfig.cpp",
+        "VtsHalAudioCoreTargetTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+    ],
+    static_libs: [
+        "android.hardware.audio.common-V1-cpp",
+        "android.hardware.audio.core-V1-cpp",
+        "android.media.audio.common.types-V1-cpp",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
new file mode 100644
index 0000000..3faa39a
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <algorithm>
+
+#include <android/media/audio/common/AudioIoFlags.h>
+#include <android/media/audio/common/AudioOutputFlags.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+
+using android::hardware::audio::core::IModule;
+using android::media::audio::common::AudioChannelLayout;
+using android::media::audio::common::AudioFormatDescription;
+using android::media::audio::common::AudioFormatType;
+using android::media::audio::common::AudioIoFlags;
+using android::media::audio::common::AudioOutputFlags;
+using android::media::audio::common::AudioPort;
+using android::media::audio::common::AudioPortConfig;
+using android::media::audio::common::AudioPortExt;
+using android::media::audio::common::AudioProfile;
+using android::media::audio::common::Int;
+
+template <typename T>
+auto findById(const std::vector<T>& v, int32_t id) {
+    return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; });
+}
+
+ModuleConfig::ModuleConfig(IModule* module) {
+    mStatus = module->getAudioPorts(&mPorts);
+    if (!mStatus.isOk()) return;
+    for (const auto& port : mPorts) {
+        if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
+        const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+        const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
+        if (devicePort.device.type.connection.empty()) {
+            // Permanently attached device.
+            if (isInput) {
+                mAttachedSourceDevicePorts.insert(port.id);
+            } else {
+                mAttachedSinkDevicePorts.insert(port.id);
+            }
+        }
+    }
+    if (!mStatus.isOk()) return;
+    mStatus = module->getAudioRoutes(&mRoutes);
+    if (!mStatus.isOk()) return;
+    mStatus = module->getAudioPortConfigs(&mInitialConfigs);
+}
+
+std::vector<AudioPort> ModuleConfig::getInputMixPorts() const {
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
+        return port.ext.getTag() == AudioPortExt::Tag::mix &&
+               port.flags.getTag() == AudioIoFlags::Tag::input;
+    });
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getOutputMixPorts() const {
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
+        return port.ext.getTag() == AudioPortExt::Tag::mix &&
+               port.flags.getTag() == AudioIoFlags::Tag::output;
+    });
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedSinkDevicesPortsForMixPort(
+        const AudioPort& mixPort) const {
+    std::vector<AudioPort> result;
+    for (const auto& route : mRoutes) {
+        if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0 &&
+            std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), mixPort.id) !=
+                    route.sourcePortIds.end()) {
+            const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+            if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt);
+        }
+    }
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedSourceDevicesPortsForMixPort(
+        const AudioPort& mixPort) const {
+    std::vector<AudioPort> result;
+    for (const auto& route : mRoutes) {
+        if (route.sinkPortId == mixPort.id) {
+            for (const auto srcId : route.sourcePortIds) {
+                if (mAttachedSourceDevicePorts.count(srcId) != 0) {
+                    const auto devicePortIt = findById<AudioPort>(mPorts, srcId);
+                    if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt);
+                }
+            }
+        }
+    }
+    return result;
+}
+
+std::optional<AudioPort> ModuleConfig::getSourceMixPortForAttachedDevice() const {
+    for (const auto& route : mRoutes) {
+        if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0) {
+            const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
+            if (mixPortIt != mPorts.end()) return *mixPortIt;
+        }
+    }
+    return {};
+}
+
+std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getNonRoutableSrcSinkPair(
+        bool isInput) const {
+    const auto mixPorts = getMixPorts(isInput);
+    std::set<std::pair<int32_t, int32_t>> allowedRoutes;
+    for (const auto& route : mRoutes) {
+        for (const auto srcPortId : route.sourcePortIds) {
+            allowedRoutes.emplace(std::make_pair(srcPortId, route.sinkPortId));
+        }
+    }
+    auto make_pair = [isInput](auto& device, auto& mix) {
+        return isInput ? std::make_pair(device, mix) : std::make_pair(mix, device);
+    };
+    for (const auto portId : isInput ? mAttachedSourceDevicePorts : mAttachedSinkDevicePorts) {
+        const auto devicePortIt = findById<AudioPort>(mPorts, portId);
+        if (devicePortIt == mPorts.end()) continue;
+        auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+        for (const auto& mixPort : mixPorts) {
+            if (std::find(allowedRoutes.begin(), allowedRoutes.end(),
+                          make_pair(portId, mixPort.id)) == allowedRoutes.end()) {
+                auto mixPortConfig = getSingleConfigForMixPort(isInput, mixPort);
+                if (mixPortConfig.has_value()) {
+                    return make_pair(devicePortConfig, mixPortConfig.value());
+                }
+            }
+        }
+    }
+    return {};
+}
+
+std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getRoutableSrcSinkPair(bool isInput) const {
+    if (isInput) {
+        for (const auto& route : mRoutes) {
+            auto srcPortIdIt = std::find_if(
+                    route.sourcePortIds.begin(), route.sourcePortIds.end(),
+                    [&](const auto& portId) { return mAttachedSourceDevicePorts.count(portId); });
+            if (srcPortIdIt == route.sourcePortIds.end()) continue;
+            const auto devicePortIt = findById<AudioPort>(mPorts, *srcPortIdIt);
+            const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+            if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue;
+            auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+            auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+            if (!mixPortConfig.has_value()) continue;
+            return std::make_pair(devicePortConfig, mixPortConfig.value());
+        }
+    } else {
+        for (const auto& route : mRoutes) {
+            if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+            const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
+            const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+            if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue;
+            auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+            auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+            if (!mixPortConfig.has_value()) continue;
+            return std::make_pair(mixPortConfig.value(), devicePortConfig);
+        }
+    }
+    return {};
+}
+
+std::vector<ModuleConfig::SrcSinkGroup> ModuleConfig::getRoutableSrcSinkGroups(bool isInput) const {
+    std::vector<SrcSinkGroup> result;
+    if (isInput) {
+        for (const auto& route : mRoutes) {
+            std::vector<int32_t> srcPortIds;
+            std::copy_if(route.sourcePortIds.begin(), route.sourcePortIds.end(),
+                         std::back_inserter(srcPortIds), [&](const auto& portId) {
+                             return mAttachedSourceDevicePorts.count(portId);
+                         });
+            if (srcPortIds.empty()) continue;
+            const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+            if (mixPortIt == mPorts.end()) continue;
+            auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+            if (!mixPortConfig.has_value()) continue;
+            std::vector<SrcSinkPair> pairs;
+            for (const auto srcPortId : srcPortIds) {
+                const auto devicePortIt = findById<AudioPort>(mPorts, srcPortId);
+                if (devicePortIt == mPorts.end()) continue;
+                // Using all configs for every source would be too much.
+                auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+                pairs.emplace_back(devicePortConfig, mixPortConfig.value());
+            }
+            if (!pairs.empty()) {
+                result.emplace_back(route, std::move(pairs));
+            }
+        }
+    } else {
+        for (const auto& route : mRoutes) {
+            if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+            const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+            if (devicePortIt == mPorts.end()) continue;
+            auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+            std::vector<SrcSinkPair> pairs;
+            for (const auto srcPortId : route.sourcePortIds) {
+                const auto mixPortIt = findById<AudioPort>(mPorts, srcPortId);
+                if (mixPortIt == mPorts.end()) continue;
+                // Using all configs for every source would be too much.
+                auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+                if (mixPortConfig.has_value()) {
+                    pairs.emplace_back(mixPortConfig.value(), devicePortConfig);
+                }
+            }
+            if (!pairs.empty()) {
+                result.emplace_back(route, std::move(pairs));
+            }
+        }
+    }
+    return result;
+}
+
+static std::vector<AudioPortConfig> combineAudioConfigs(const AudioPort& port,
+                                                        const AudioProfile& profile) {
+    std::vector<AudioPortConfig> configs;
+    configs.reserve(profile.channelMasks.size() * profile.sampleRates.size());
+    for (auto channelMask : profile.channelMasks) {
+        for (auto sampleRate : profile.sampleRates) {
+            AudioPortConfig config{};
+            config.portId = port.id;
+            Int sr;
+            sr.value = sampleRate;
+            config.sampleRate = sr;
+            config.channelMask = channelMask;
+            config.format = profile.format;
+            config.ext = port.ext;
+            configs.push_back(config);
+        }
+    }
+    return configs;
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateInputAudioMixPortConfigs(
+        const std::vector<AudioPort>& ports, bool singleProfile) const {
+    std::vector<AudioPortConfig> result;
+    for (const auto& mixPort : ports) {
+        if (getAttachedSourceDevicesPortsForMixPort(mixPort).empty()) {
+            continue;  // no attached devices
+        }
+        for (const auto& profile : mixPort.profiles) {
+            if (profile.format.type == AudioFormatType::DEFAULT || profile.sampleRates.empty() ||
+                profile.channelMasks.empty()) {
+                continue;  // dynamic profile
+            }
+            auto configs = combineAudioConfigs(mixPort, profile);
+            for (auto& config : configs) {
+                config.flags = mixPort.flags;
+                result.push_back(config);
+                if (singleProfile) return result;
+            }
+        }
+    }
+    return result;
+}
+
+static std::tuple<AudioIoFlags, bool> generateOutFlags(const AudioPort& mixPort) {
+    static const AudioIoFlags offloadFlags = AudioIoFlags::make<AudioIoFlags::Tag::output>(
+            (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD)) |
+            (1 << static_cast<int>(AudioOutputFlags::DIRECT)));
+    const bool isOffload = (mixPort.flags.get<AudioIoFlags::Tag::output>() &
+                            (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD))) != 0;
+    return {isOffload ? offloadFlags : mixPort.flags, isOffload};
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateOutputAudioMixPortConfigs(
+        const std::vector<AudioPort>& ports, bool singleProfile) const {
+    std::vector<AudioPortConfig> result;
+    for (const auto& mixPort : ports) {
+        if (getAttachedSinkDevicesPortsForMixPort(mixPort).empty()) {
+            continue;  // no attached devices
+        }
+        auto [flags, isOffload] = generateOutFlags(mixPort);
+        (void)isOffload;
+        for (const auto& profile : mixPort.profiles) {
+            if (profile.format.type == AudioFormatType::DEFAULT) continue;
+            auto configs = combineAudioConfigs(mixPort, profile);
+            for (auto& config : configs) {
+                // Some combinations of flags declared in the config file require special
+                // treatment.
+                // if (isOffload) {
+                //     config.offloadInfo.info(generateOffloadInfo(config.base));
+                // }
+                config.flags = flags;
+                result.push_back(config);
+                if (singleProfile) return result;
+            }
+        }
+    }
+    return result;
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateAudioDevicePortConfigs(
+        const std::vector<AudioPort>& ports, bool singleProfile) const {
+    std::vector<AudioPortConfig> result;
+    for (const auto& devicePort : ports) {
+        const size_t resultSizeBefore = result.size();
+        for (const auto& profile : devicePort.profiles) {
+            auto configs = combineAudioConfigs(devicePort, profile);
+            result.insert(result.end(), configs.begin(), configs.end());
+            if (singleProfile && !result.empty()) return result;
+        }
+        if (resultSizeBefore == result.size()) {
+            AudioPortConfig empty;
+            empty.portId = devicePort.id;
+            empty.ext = devicePort.ext;
+            result.push_back(empty);
+        }
+        if (singleProfile) return result;
+    }
+    return result;
+}
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
new file mode 100644
index 0000000..2e86b97
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <android/hardware/audio/core/AudioRoute.h>
+#include <android/hardware/audio/core/IModule.h>
+#include <android/media/audio/common/AudioPort.h>
+#include <binder/Status.h>
+
+class ModuleConfig {
+  public:
+    using SrcSinkPair = std::pair<android::media::audio::common::AudioPortConfig,
+                                  android::media::audio::common::AudioPortConfig>;
+    using SrcSinkGroup =
+            std::pair<android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
+
+    explicit ModuleConfig(android::hardware::audio::core::IModule* module);
+    android::binder::Status getStatus() const { return mStatus; }
+    std::string getError() const { return mStatus.toString8().c_str(); }
+
+    std::vector<android::media::audio::common::AudioPort> getInputMixPorts() const;
+    std::vector<android::media::audio::common::AudioPort> getOutputMixPorts() const;
+    std::vector<android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
+        return isInput ? getInputMixPorts() : getOutputMixPorts();
+    }
+
+    std::vector<android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+            bool isInput, const android::media::audio::common::AudioPort& mixPort) const {
+        return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort)
+                       : getAttachedSinkDevicesPortsForMixPort(mixPort);
+    }
+    std::vector<android::media::audio::common::AudioPort> getAttachedSinkDevicesPortsForMixPort(
+            const android::media::audio::common::AudioPort& mixPort) const;
+    std::vector<android::media::audio::common::AudioPort> getAttachedSourceDevicesPortsForMixPort(
+            const android::media::audio::common::AudioPort& mixPort) const;
+    std::optional<android::media::audio::common::AudioPort> getSourceMixPortForAttachedDevice()
+            const;
+
+    std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
+    std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
+    std::vector<SrcSinkGroup> getRoutableSrcSinkGroups(bool isInput) const;
+
+    std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts() const {
+        auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false);
+        auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
+        inputs.insert(inputs.end(), outputs.begin(), outputs.end());
+        return inputs;
+    }
+    std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+            bool isInput) const {
+        return isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), false)
+                       : generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
+    }
+    std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+            bool isInput, const android::media::audio::common::AudioPort& port) const {
+        return isInput ? generateInputAudioMixPortConfigs({port}, false)
+                       : generateOutputAudioMixPortConfigs({port}, false);
+    }
+    std::optional<android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+            bool isInput) const {
+        const auto config = isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), true)
+                                    : generateOutputAudioMixPortConfigs(getOutputMixPorts(), true);
+        // TODO: Avoid returning configs for offload since they require an extra
+        //       argument to openOutputStream.
+        if (!config.empty()) {
+            return *config.begin();
+        } else {
+            return {};
+        }
+    }
+    std::optional<android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+            bool isInput, const android::media::audio::common::AudioPort& port) const {
+        const auto config = isInput ? generateInputAudioMixPortConfigs({port}, true)
+                                    : generateOutputAudioMixPortConfigs({port}, true);
+        if (!config.empty()) {
+            return *config.begin();
+        } else {
+            return {};
+        }
+    }
+
+    android::media::audio::common::AudioPortConfig getSingleConfigForDevicePort(
+            const android::media::audio::common::AudioPort& port) const {
+        for (const auto& config : mInitialConfigs) {
+            if (config.portId == port.id) return config;
+        }
+        const auto config = generateAudioDevicePortConfigs({port}, true);
+        return *config.begin();
+    }
+
+  private:
+    std::vector<android::media::audio::common::AudioPortConfig> generateInputAudioMixPortConfigs(
+            const std::vector<android::media::audio::common::AudioPort>& ports,
+            bool singleProfile) const;
+    std::vector<android::media::audio::common::AudioPortConfig> generateOutputAudioMixPortConfigs(
+            const std::vector<android::media::audio::common::AudioPort>& ports,
+            bool singleProfile) const;
+
+    // Unlike MixPorts, the generator for DevicePorts always returns a non-empty
+    // vector for a non-empty input port list. If there are no profiles in the
+    // port, a vector with an empty config is returned.
+    std::vector<android::media::audio::common::AudioPortConfig> generateAudioDevicePortConfigs(
+            const std::vector<android::media::audio::common::AudioPort>& ports,
+            bool singleProfile) const;
+
+    android::binder::Status mStatus = android::binder::Status::ok();
+    std::vector<android::media::audio::common::AudioPort> mPorts;
+    std::vector<android::media::audio::common::AudioPortConfig> mInitialConfigs;
+    std::set<int32_t> mAttachedSinkDevicePorts;
+    std::set<int32_t> mAttachedSourceDevicePorts;
+    std::vector<android::hardware::audio::core::AudioRoute> mRoutes;
+};
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
new file mode 100644
index 0000000..cadeb0c
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -0,0 +1,1027 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <algorithm>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <set>
+#include <string>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/properties.h>
+#include <android/hardware/audio/core/IConfig.h>
+#include <android/hardware/audio/core/IModule.h>
+#include <android/media/audio/common/AudioIoFlags.h>
+#include <android/media/audio/common/AudioOutputFlags.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+using android::binder::Status;
+using android::hardware::audio::common::PlaybackTrackMetadata;
+using android::hardware::audio::common::RecordTrackMetadata;
+using android::hardware::audio::common::SinkMetadata;
+using android::hardware::audio::common::SourceMetadata;
+using android::hardware::audio::core::AudioPatch;
+using android::hardware::audio::core::AudioRoute;
+using android::hardware::audio::core::IModule;
+using android::hardware::audio::core::IStreamIn;
+using android::hardware::audio::core::IStreamOut;
+using android::media::audio::common::AudioContentType;
+using android::media::audio::common::AudioDevice;
+using android::media::audio::common::AudioDeviceType;
+using android::media::audio::common::AudioIoFlags;
+using android::media::audio::common::AudioOutputFlags;
+using android::media::audio::common::AudioPort;
+using android::media::audio::common::AudioPortConfig;
+using android::media::audio::common::AudioPortDeviceExt;
+using android::media::audio::common::AudioPortExt;
+using android::media::audio::common::AudioSource;
+using android::media::audio::common::AudioUsage;
+
+template <typename T>
+auto findById(std::vector<T>& v, int32_t id) {
+    return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
+}
+
+template <typename C>
+std::vector<int32_t> getNonExistentIds(const C& allIds) {
+    if (allIds.empty()) {
+        return std::vector<int32_t>{-1, 0, 1};
+    }
+    std::vector<int32_t> nonExistentIds;
+    nonExistentIds.push_back(*std::min_element(allIds.begin(), allIds.end()) - 1);
+    nonExistentIds.push_back(*std::max_element(allIds.begin(), allIds.end()) + 1);
+    return nonExistentIds;
+}
+
+struct AidlDeathRecipient : IBinder::DeathRecipient {
+    std::mutex mutex;
+    std::condition_variable condition;
+    bool fired = false;
+    wp<IBinder> who;
+
+    void binderDied(const wp<IBinder>& who) override {
+        std::unique_lock<std::mutex> lock(mutex);
+        fired = true;
+        this->who = who;
+        condition.notify_one();
+    };
+
+    bool waitForFired(int timeoutMs) {
+        std::unique_lock<std::mutex> lock(mutex);
+        condition.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return fired; });
+        return fired;
+    }
+};
+
+template <typename T>
+struct IsInput {
+    constexpr operator bool() const;
+};
+
+template <>
+constexpr IsInput<IStreamIn>::operator bool() const {
+    return true;
+}
+template <>
+constexpr IsInput<IStreamOut>::operator bool() const {
+    return false;
+}
+
+class AudioCoreModule : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); }
+
+    void ConnectToService() {
+        module = android::waitForDeclaredService<IModule>(String16(GetParam().c_str()));
+        ASSERT_NE(module, nullptr);
+    }
+
+    void RestartService() {
+        ASSERT_NE(module, nullptr);
+        moduleConfig.reset();
+        deathHandler = sp<AidlDeathRecipient>::make();
+        ASSERT_EQ(NO_ERROR, IModule::asBinder(module)->linkToDeath(deathHandler));
+        ASSERT_TRUE(base::SetProperty("sys.audio.restart.hal", "1"));
+        EXPECT_TRUE(deathHandler->waitForFired(3000));
+        deathHandler = nullptr;
+        ASSERT_NO_FATAL_FAILURE(ConnectToService());
+    }
+
+    template <typename Entity>
+    void GetAllEntityIds(std::set<int32_t>* entityIds,
+                         Status (IModule::*getter)(std::vector<Entity>*),
+                         const std::string& errorMessage) {
+        std::vector<Entity> entities;
+        {
+            Status status = (module.get()->*getter)(&entities);
+            ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+        }
+        std::transform(entities.begin(), entities.end(),
+                       std::inserter(*entityIds, entityIds->begin()),
+                       [](const auto& entity) { return entity.id; });
+        EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
+    }
+
+    void GetAllPatchIds(std::set<int32_t>* patchIds) {
+        return GetAllEntityIds<AudioPatch>(
+                patchIds, &IModule::getAudioPatches,
+                "IDs of audio patches returned by IModule.getAudioPatches are not unique");
+    }
+
+    void GetAllPortIds(std::set<int32_t>* portIds) {
+        return GetAllEntityIds<AudioPort>(
+                portIds, &IModule::getAudioPorts,
+                "IDs of audio ports returned by IModule.getAudioPorts are not unique");
+    }
+
+    void GetAllPortConfigIds(std::set<int32_t>* portConfigIds) {
+        return GetAllEntityIds<AudioPortConfig>(
+                portConfigIds, &IModule::getAudioPortConfigs,
+                "IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique");
+    }
+
+    void SetUpModuleConfig() {
+        if (moduleConfig == nullptr) {
+            moduleConfig = std::make_unique<ModuleConfig>(module.get());
+            ASSERT_EQ(Status::EX_NONE, moduleConfig->getStatus().exceptionCode())
+                    << "ModuleConfig init error: " << moduleConfig->getError();
+        }
+    }
+
+    sp<IModule> module;
+    sp<AidlDeathRecipient> deathHandler;
+    std::unique_ptr<ModuleConfig> moduleConfig;
+};
+
+// For consistency, WithAudioPortConfig can start both with a non-existent
+// port config, and with an existing one. Existence is determined by the
+// id of the provided config. If it's not 0, then WithAudioPortConfig is
+// essentially a no-op wrapper.
+class WithAudioPortConfig {
+  public:
+    WithAudioPortConfig() {}
+    explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {}
+    ~WithAudioPortConfig() {
+        if (mModule != nullptr) {
+            Status status = mModule->resetAudioPortConfig(getId());
+            EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+                    << status << "; port config id " << getId();
+        }
+    }
+    void SetUp(IModule* module) {
+        ASSERT_NE(AudioPortExt::Tag::unspecified, mInitialConfig.ext.getTag())
+                << "config: " << mInitialConfig.toString();
+        // Negotiation is allowed for device ports because the HAL module is
+        // allowed to provide an empty profiles list for attached devices.
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpImpl(module, mInitialConfig.ext.getTag() == AudioPortExt::Tag::device));
+    }
+    int32_t getId() const { return mConfig.id; }
+    const AudioPortConfig& get() const { return mConfig; }
+
+  private:
+    void SetUpImpl(IModule* module, bool negotiate) {
+        if (mInitialConfig.id == 0) {
+            AudioPortConfig suggested;
+            bool applied = false;
+            Status status = module->setAudioPortConfig(mInitialConfig, &suggested, &applied);
+            ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+                    << status << "; Config: " << mInitialConfig.toString();
+            if (!applied && negotiate) {
+                mInitialConfig = suggested;
+                ASSERT_NO_FATAL_FAILURE(SetUpImpl(module, false))
+                        << " while applying suggested config: " << suggested.toString();
+            } else {
+                ASSERT_TRUE(applied) << "Suggested: " << suggested.toString();
+                mConfig = suggested;
+                mModule = module;
+            }
+        } else {
+            mConfig = mInitialConfig;
+        }
+    }
+
+    AudioPortConfig mInitialConfig;
+    IModule* mModule = nullptr;
+    AudioPortConfig mConfig;
+};
+
+template <typename Stream>
+class WithStream {
+  public:
+    WithStream() {}
+    explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
+    ~WithStream() {
+        if (mStream != nullptr) {
+            Status status = mStream->close();
+            EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+                    << status << "; port config id " << getPortId();
+        }
+    }
+    void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); }
+    Status SetUpNoChecks(IModule* module) { return SetUpNoChecks(module, mPortConfig.get()); }
+    Status SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig);
+    void SetUp(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+        Status status = SetUpNoChecks(module);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+                << status << "; port config id " << getPortId();
+        ASSERT_NE(nullptr, mStream) << "; port config id " << getPortId();
+    }
+    Stream* get() const { return mStream.get(); }
+    const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
+    int32_t getPortId() const { return mPortConfig.getId(); }
+
+  private:
+    WithAudioPortConfig mPortConfig;
+    sp<Stream> mStream;
+};
+
+template <>
+Status WithStream<IStreamIn>::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig) {
+    RecordTrackMetadata trackMeta;
+    trackMeta.source = AudioSource::MIC;
+    trackMeta.gain = 1.0;
+    trackMeta.channelMask = portConfig.channelMask.value();
+    SinkMetadata metadata;
+    metadata.tracks.push_back(trackMeta);
+    return module->openInputStream(portConfig.id, metadata, &mStream);
+}
+
+template <>
+Status WithStream<IStreamOut>::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig) {
+    PlaybackTrackMetadata trackMeta;
+    trackMeta.usage = AudioUsage::MEDIA;
+    trackMeta.contentType = AudioContentType::MUSIC;
+    trackMeta.gain = 1.0;
+    trackMeta.channelMask = portConfig.channelMask.value();
+    SourceMetadata metadata;
+    metadata.tracks.push_back(trackMeta);
+    return module->openOutputStream(portConfig.id, metadata, {}, &mStream);
+}
+
+class WithAudioPatch {
+  public:
+    WithAudioPatch() {}
+    WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
+        : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
+    ~WithAudioPatch() {
+        if (mModule != nullptr && mPatch.id != 0) {
+            Status status = mModule->resetAudioPatch(mPatch.id);
+            EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+                    << status << "; patch id " << getId();
+        }
+    }
+    void SetUpPortConfigs(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module));
+        ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module));
+    }
+    Status SetUpNoChecks(IModule* module) {
+        mModule = module;
+        mPatch.sourcePortConfigIds = std::vector<int32_t>{mSrcPortConfig.getId()};
+        mPatch.sinkPortConfigIds = std::vector<int32_t>{mSinkPortConfig.getId()};
+        return mModule->setAudioPatch(mPatch, &mPatch);
+    }
+    void SetUp(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigs(module));
+        Status status = SetUpNoChecks(module);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+                << status << "; source port config id " << mSrcPortConfig.getId()
+                << "; sink port config id " << mSinkPortConfig.getId();
+    }
+    int32_t getId() const { return mPatch.id; }
+    const AudioPatch& get() const { return mPatch; }
+
+  private:
+    WithAudioPortConfig mSrcPortConfig;
+    WithAudioPortConfig mSinkPortConfig;
+    IModule* mModule = nullptr;
+    AudioPatch mPatch;
+};
+
+TEST_P(AudioCoreModule, Published) {
+    // SetUp must complete with no failures.
+}
+
+TEST_P(AudioCoreModule, CanBeRestarted) {
+    ASSERT_NO_FATAL_FAILURE(RestartService());
+}
+
+TEST_P(AudioCoreModule, PortIdsAreUnique) {
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+}
+
+TEST_P(AudioCoreModule, GetAudioPortsIsStatic) {
+    std::vector<AudioPort> ports1;
+    {
+        Status status = module->getAudioPorts(&ports1);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    std::vector<AudioPort> ports2;
+    {
+        Status status = module->getAudioPorts(&ports2);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    ASSERT_EQ(ports1.size(), ports2.size())
+            << "Sizes of audio port arrays do not match across calls to getAudioPorts";
+    std::sort(ports1.begin(), ports1.end());
+    std::sort(ports2.begin(), ports2.end());
+    EXPECT_EQ(ports1, ports2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) {
+    std::vector<AudioRoute> routes1;
+    {
+        Status status = module->getAudioRoutes(&routes1);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    std::vector<AudioRoute> routes2;
+    {
+        Status status = module->getAudioRoutes(&routes2);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    ASSERT_EQ(routes1.size(), routes2.size())
+            << "Sizes of audio route arrays do not match across calls to getAudioRoutes";
+    std::sort(routes1.begin(), routes1.end());
+    std::sort(routes2.begin(), routes2.end());
+    EXPECT_EQ(routes1, routes2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesAreValid) {
+    std::vector<AudioRoute> routes;
+    {
+        Status status = module->getAudioRoutes(&routes);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    for (const auto& route : routes) {
+        std::set<int32_t> sources(route.sourcePortIds.begin(), route.sourcePortIds.end());
+        EXPECT_NE(0, sources.size())
+                << "empty audio port sinks in the audio route: " << route.toString();
+        EXPECT_EQ(sources.size(), route.sourcePortIds.size())
+                << "IDs of audio port sinks are not unique in the audio route: "
+                << route.toString();
+    }
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesPortIdsAreValid) {
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+    std::vector<AudioRoute> routes;
+    {
+        Status status = module->getAudioRoutes(&routes);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    for (const auto& route : routes) {
+        EXPECT_EQ(1, portIds.count(route.sinkPortId))
+                << route.sinkPortId << " sink port id is unknown";
+        for (const auto& source : route.sourcePortIds) {
+            EXPECT_EQ(1, portIds.count(source)) << source << " source port id is unknown";
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, CheckDevicePorts) {
+    std::vector<AudioPort> ports;
+    {
+        Status status = module->getAudioPorts(&ports);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    std::optional<int32_t> defaultOutput, defaultInput;
+    std::set<AudioDevice> inputs, outputs;
+    const int defaultDeviceFlag = 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE;
+    for (const auto& port : ports) {
+        if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
+        const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+        EXPECT_NE(AudioDeviceType::NONE, devicePort.device.type.type);
+        EXPECT_NE(AudioDeviceType::IN_DEFAULT, devicePort.device.type.type);
+        EXPECT_NE(AudioDeviceType::OUT_DEFAULT, devicePort.device.type.type);
+        if (devicePort.device.type.type > AudioDeviceType::IN_DEFAULT &&
+            devicePort.device.type.type < AudioDeviceType::OUT_DEFAULT) {
+            EXPECT_EQ(AudioIoFlags::Tag::input, port.flags.getTag());
+        } else if (devicePort.device.type.type > AudioDeviceType::OUT_DEFAULT) {
+            EXPECT_EQ(AudioIoFlags::Tag::output, port.flags.getTag());
+        }
+        EXPECT_FALSE((devicePort.flags & defaultDeviceFlag) != 0 &&
+                     !devicePort.device.type.connection.empty())
+                << "Device port " << port.id
+                << " must be permanently attached to be set as default";
+        if ((devicePort.flags & defaultDeviceFlag) != 0) {
+            if (port.flags.getTag() == AudioIoFlags::Tag::output) {
+                EXPECT_FALSE(defaultOutput.has_value())
+                        << "At least two output device ports are declared as default: "
+                        << defaultOutput.value() << " and " << port.id;
+                defaultOutput = port.id;
+                EXPECT_EQ(0, outputs.count(devicePort.device))
+                        << "Non-unique output device: " << devicePort.device.toString();
+                outputs.insert(devicePort.device);
+            } else if (port.flags.getTag() == AudioIoFlags::Tag::input) {
+                EXPECT_FALSE(defaultInput.has_value())
+                        << "At least two input device ports are declared as default: "
+                        << defaultInput.value() << " and " << port.id;
+                defaultInput = port.id;
+                EXPECT_EQ(0, inputs.count(devicePort.device))
+                        << "Non-unique input device: " << devicePort.device.toString();
+                inputs.insert(devicePort.device);
+            } else {
+                FAIL() << "Invalid AudioIoFlags Tag: " << toString(port.flags.getTag());
+            }
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, CheckMixPorts) {
+    std::vector<AudioPort> ports;
+    {
+        Status status = module->getAudioPorts(&ports);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    std::optional<int32_t> primaryMixPort;
+    constexpr int primaryOutputFlag = 1 << static_cast<int>(AudioOutputFlags::PRIMARY);
+    for (const auto& port : ports) {
+        if (port.ext.getTag() != AudioPortExt::Tag::mix) continue;
+        const auto& mixPort = port.ext.get<AudioPortExt::Tag::mix>();
+        if (port.flags.getTag() == AudioIoFlags::Tag::output &&
+            ((port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0)) {
+            EXPECT_FALSE(primaryMixPort.has_value())
+                    << "At least two mix ports have PRIMARY flag set: " << primaryMixPort.value()
+                    << " and " << port.id;
+            primaryMixPort = port.id;
+            EXPECT_EQ(1, mixPort.maxOpenStreamCount)
+                    << "Primary mix port " << port.id << " can not have maxOpenStreamCount "
+                    << mixPort.maxOpenStreamCount;
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, GetAudioPort) {
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+    if (portIds.empty()) {
+        GTEST_SKIP() << "No ports in the module.";
+    }
+    for (const auto portId : portIds) {
+        AudioPort port;
+        Status status = module->getAudioPort(portId, &port);
+        EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+        EXPECT_EQ(portId, port.id);
+    }
+    for (const auto portId : getNonExistentIds(portIds)) {
+        AudioPort port;
+        Status status = module->getAudioPort(portId, &port);
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " returned for port ID " << portId;
+    }
+}
+
+TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
+    std::set<int32_t> portConfigIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+    for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+        {
+            sp<IStreamIn> stream;
+            Status status = module->openInputStream(portConfigId, {}, &stream);
+            EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                    << status << " openInputStream returned for port config ID " << portConfigId;
+            EXPECT_EQ(nullptr, stream);
+        }
+        {
+            sp<IStreamOut> stream;
+            Status status = module->openOutputStream(portConfigId, {}, {}, &stream);
+            EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                    << status << " openOutputStream returned for port config ID " << portConfigId;
+            EXPECT_EQ(nullptr, stream);
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, PortConfigIdsAreUnique) {
+    std::set<int32_t> portConfigIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+}
+
+TEST_P(AudioCoreModule, PortConfigPortIdsAreValid) {
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+    std::vector<AudioPortConfig> portConfigs;
+    {
+        Status status = module->getAudioPortConfigs(&portConfigs);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    for (const auto& config : portConfigs) {
+        EXPECT_EQ(1, portIds.count(config.portId))
+                << config.portId << " port id is unknown, config id " << config.id;
+    }
+}
+
+TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) {
+    std::set<int32_t> portConfigIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+    for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+        Status status = module->resetAudioPortConfig(portConfigId);
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " returned for port config ID " << portConfigId;
+    }
+}
+
+// Verify that for the audio port configs provided by the HAL after init, resetting
+// the config does not delete it, but brings it back to the initial config.
+TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) {
+    std::vector<AudioPortConfig> portConfigsBefore;
+    {
+        Status status = module->getAudioPortConfigs(&portConfigsBefore);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    // TODO: Change port configs according to port profiles.
+    for (const auto& c : portConfigsBefore) {
+        Status status = module->resetAudioPortConfig(c.id);
+        EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+                << status << " returned for port config ID " << c.id;
+    }
+    std::vector<AudioPortConfig> portConfigsAfter;
+    {
+        Status status = module->getAudioPortConfigs(&portConfigsAfter);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+    }
+    for (const auto& c : portConfigsBefore) {
+        auto afterIt = findById<AudioPortConfig>(portConfigsAfter, c.id);
+        EXPECT_NE(portConfigsAfter.end(), afterIt)
+                << " port config ID " << c.id << " was removed by reset";
+        if (afterIt != portConfigsAfter.end()) {
+            EXPECT_EQ(c, *afterIt);
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    auto srcMixPort = moduleConfig->getSourceMixPortForAttachedDevice();
+    if (!srcMixPort.has_value()) {
+        GTEST_SKIP() << "No mix port for attached output devices";
+    }
+    AudioPortConfig portConfig;
+    AudioPortConfig suggestedConfig;
+    portConfig.portId = srcMixPort.value().id;
+    {
+        bool applied = true;
+        Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+                << status << "; Config: " << portConfig.toString();
+        EXPECT_FALSE(applied);
+    }
+    EXPECT_EQ(0, suggestedConfig.id);
+    EXPECT_TRUE(suggestedConfig.sampleRate.has_value());
+    EXPECT_TRUE(suggestedConfig.channelMask.has_value());
+    EXPECT_TRUE(suggestedConfig.format.has_value());
+    EXPECT_TRUE(suggestedConfig.flags.has_value());
+    WithAudioPortConfig applied(suggestedConfig);
+    ASSERT_NO_FATAL_FAILURE(applied.SetUp(module.get()));
+    const AudioPortConfig& appliedConfig = applied.get();
+    EXPECT_NE(0, appliedConfig.id);
+    EXPECT_TRUE(appliedConfig.sampleRate.has_value());
+    EXPECT_EQ(suggestedConfig.sampleRate.value(), appliedConfig.sampleRate.value());
+    EXPECT_TRUE(appliedConfig.channelMask.has_value());
+    EXPECT_EQ(suggestedConfig.channelMask.value(), appliedConfig.channelMask.value());
+    EXPECT_TRUE(appliedConfig.format.has_value());
+    EXPECT_EQ(suggestedConfig.format.value(), appliedConfig.format.value());
+    EXPECT_TRUE(appliedConfig.flags.has_value());
+    EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value());
+}
+
+TEST_P(AudioCoreModule, SetAllStaticAudioPortConfigs) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts();
+    for (const auto& config : allPortConfigs) {
+        ASSERT_NE(0, config.portId);
+        WithAudioPortConfig portConfig(config);
+        ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+        EXPECT_EQ(config.portId, portConfig.get().portId);
+        std::vector<AudioPortConfig> retrievedPortConfigs;
+        {
+            Status status = module->getAudioPortConfigs(&retrievedPortConfigs);
+            ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+        }
+        const int32_t portConfigId = portConfig.getId();
+        auto configIt = std::find_if(
+                retrievedPortConfigs.begin(), retrievedPortConfigs.end(),
+                [&portConfigId](const auto& retrConf) { return retrConf.id == portConfigId; });
+        EXPECT_NE(configIt, retrievedPortConfigs.end())
+                << "Port config id returned by setAudioPortConfig: " << portConfigId
+                << " is not found in the list returned by getPortConfigsForMixPorts";
+        if (configIt != retrievedPortConfigs.end()) {
+            EXPECT_EQ(portConfig.get(), *configIt)
+                    << "Port config returned by getPortConfigsForMixPorts: " << configIt->toString()
+                    << " is not the same as returned by setAudioPortConfig: "
+                    << portConfig.get().toString();
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) {
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+    for (const auto portId : getNonExistentIds(portIds)) {
+        AudioPortConfig portConfig, suggestedConfig;
+        bool applied;
+        portConfig.portId = portId;
+        Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " returned for port ID " << portId;
+        EXPECT_FALSE(suggestedConfig.format.has_value());
+        EXPECT_FALSE(suggestedConfig.channelMask.has_value());
+        EXPECT_FALSE(suggestedConfig.sampleRate.has_value());
+    }
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) {
+    std::set<int32_t> portConfigIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+    for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+        AudioPortConfig portConfig, suggestedConfig;
+        bool applied;
+        portConfig.id = portConfigId;
+        Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " returned for port config ID " << portConfigId;
+        EXPECT_FALSE(suggestedConfig.format.has_value());
+        EXPECT_FALSE(suggestedConfig.channelMask.has_value());
+        EXPECT_FALSE(suggestedConfig.sampleRate.has_value());
+    }
+}
+
+template <typename Stream>
+class AudioStream : public AudioCoreModule {
+  public:
+    static std::string direction(bool capitalize);
+
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+        ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    }
+
+    void CloseTwice() {
+        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+        if (!portConfig.has_value()) {
+            GTEST_SKIP() << "No mix port for attached devices";
+        }
+        sp<Stream> heldStream;
+        {
+            WithStream<Stream> stream(portConfig.value());
+            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+            heldStream = stream.get();
+        }
+        Status status = heldStream->close();
+        EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+                << status << " when closing the stream twice";
+    }
+
+    void OpenAllConfigs() {
+        const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IsInput<Stream>());
+        for (const auto& portConfig : allPortConfigs) {
+            WithStream<Stream> stream(portConfig);
+            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+        }
+    }
+
+    void OpenOverMaxCount() {
+        constexpr bool isInput = IsInput<Stream>();
+        auto ports = moduleConfig->getMixPorts(isInput);
+        bool hasSingleRun = false;
+        for (const auto& port : ports) {
+            const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
+            if (maxStreamCount == 0 ||
+                moduleConfig->getAttachedDevicesPortsForMixPort(isInput, port).empty()) {
+                // No restrictions or no permanently attached devices.
+                continue;
+            }
+            auto portConfigs = moduleConfig->getPortConfigsForMixPorts(isInput, port);
+            if (portConfigs.size() < maxStreamCount + 1) {
+                // Not able to open a sufficient number of streams for this port.
+                continue;
+            }
+            hasSingleRun = true;
+            std::optional<WithStream<Stream>> streamWraps[maxStreamCount + 1];
+            for (size_t i = 0; i <= maxStreamCount; ++i) {
+                streamWraps[i].emplace(portConfigs[i]);
+                WithStream<Stream>& stream = streamWraps[i].value();
+                if (i < maxStreamCount) {
+                    ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+                } else {
+                    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+                    Status status = stream.SetUpNoChecks(module.get());
+                    EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+                            << status << " open" << direction(true)
+                            << "Stream"
+                               " returned for port config ID "
+                            << stream.getPortId() << ", maxOpenStreamCount is " << maxStreamCount;
+                }
+            }
+        }
+        if (!hasSingleRun) {
+            GTEST_SKIP() << "Not enough " << direction(false)
+                         << " ports to test max open stream count";
+        }
+    }
+
+    void OpenInvalidDirection() {
+        // Important! The direction of the port config must be reversed.
+        const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IsInput<Stream>());
+        if (!portConfig.has_value()) {
+            GTEST_SKIP() << "No mix port for attached devices";
+        }
+        WithStream<Stream> stream(portConfig.value());
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+        Status status = stream.SetUpNoChecks(module.get());
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " open" << direction(true) << "Stream returned for port config ID "
+                << stream.getPortId();
+        EXPECT_EQ(nullptr, stream.get());
+    }
+
+    void OpenTwiceSamePortConfig() {
+        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+        if (!portConfig.has_value()) {
+            GTEST_SKIP() << "No mix port for attached devices";
+        }
+        EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value()));
+    }
+
+    void ResetPortConfigWithOpenStream() {
+        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+        if (!portConfig.has_value()) {
+            GTEST_SKIP() << "No mix port for attached devices";
+        }
+        WithStream<Stream> stream(portConfig.value());
+        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+        Status status = module->resetAudioPortConfig(stream.getPortId());
+        EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+                << status << " returned for port config ID " << stream.getPortId();
+    }
+
+    void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
+        WithStream<Stream> stream1(portConfig);
+        ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get()));
+        WithStream<Stream> stream2;
+        Status status = stream2.SetUpNoChecks(module.get(), stream1.getPortConfig());
+        EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+                << status << " when opening " << direction(false)
+                << " stream twice for the same port config ID " << stream1.getPortId();
+    }
+};
+using AudioStreamIn = AudioStream<IStreamIn>;
+using AudioStreamOut = AudioStream<IStreamOut>;
+
+template <>
+std::string AudioStreamIn::direction(bool capitalize) {
+    return capitalize ? "Input" : "input";
+}
+template <>
+std::string AudioStreamOut::direction(bool capitalize) {
+    return capitalize ? "Output" : "output";
+}
+
+#define TEST_IO_STREAM(method_name)                                                \
+    TEST_P(AudioStreamIn, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); } \
+    TEST_P(AudioStreamOut, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); }
+
+TEST_IO_STREAM(CloseTwice);
+TEST_IO_STREAM(OpenAllConfigs);
+TEST_IO_STREAM(OpenInvalidDirection);
+TEST_IO_STREAM(OpenOverMaxCount);
+TEST_IO_STREAM(OpenTwiceSamePortConfig);
+TEST_IO_STREAM(ResetPortConfigWithOpenStream);
+
+TEST_P(AudioStreamOut, OpenTwicePrimary) {
+    const auto mixPorts = moduleConfig->getMixPorts(false);
+    auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) {
+        constexpr int primaryOutputFlag = 1 << static_cast<int>(AudioOutputFlags::PRIMARY);
+        return port.flags.getTag() == AudioIoFlags::Tag::output &&
+               ((port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0);
+    });
+    if (primaryPortIt == mixPorts.end()) {
+        GTEST_SKIP() << "No primary mix port";
+    }
+    if (moduleConfig->getAttachedSinkDevicesPortsForMixPort(*primaryPortIt).empty()) {
+        GTEST_SKIP() << "Primary mix port can not be routed to any of attached devices";
+    }
+    const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *primaryPortIt);
+    ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for the primary mix port";
+    EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value()));
+}
+
+// Tests specific to audio patches. The fixure class is named 'AudioModulePatch'
+// to avoid clashing with 'AudioPatch' class.
+class AudioModulePatch : public AudioCoreModule {
+  public:
+    static std::string direction(bool isInput, bool capitalize) {
+        return isInput ? (capitalize ? "Input" : "input") : (capitalize ? "Output" : "output");
+    }
+
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+        ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    }
+
+    void SetInvalidPatchHelper(int32_t expectedException, const std::vector<int32_t>& sources,
+                               const std::vector<int32_t>& sinks) {
+        AudioPatch patch;
+        patch.sourcePortConfigIds = sources;
+        patch.sinkPortConfigIds = sinks;
+        Status status = module->setAudioPatch(patch, &patch);
+        ASSERT_EQ(expectedException, status.exceptionCode())
+                << status << ": patch source ids: " << android::internal::ToString(sources)
+                << "; sink ids: " << android::internal::ToString(sinks);
+    }
+
+    void ResetPortConfigUsedByPatch(bool isInput) {
+        auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+        if (srcSinkGroups.empty()) {
+            GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+        }
+        auto srcSinkGroup = *srcSinkGroups.begin();
+        auto srcSink = *srcSinkGroup.second.begin();
+        WithAudioPatch patch(srcSink.first, srcSink.second);
+        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+        std::vector<int32_t> sourceAndSinkPortConfigIds(patch.get().sourcePortConfigIds);
+        sourceAndSinkPortConfigIds.insert(sourceAndSinkPortConfigIds.end(),
+                                          patch.get().sinkPortConfigIds.begin(),
+                                          patch.get().sinkPortConfigIds.end());
+        for (const auto portConfigId : sourceAndSinkPortConfigIds) {
+            Status status = module->resetAudioPortConfig(portConfigId);
+            EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+                    << status << " returned for port config ID " << portConfigId;
+        }
+    }
+
+    void SetInvalidPatch(bool isInput) {
+        auto srcSinkPair = moduleConfig->getRoutableSrcSinkPair(isInput);
+        if (!srcSinkPair.has_value()) {
+            GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+        }
+        WithAudioPortConfig srcPortConfig(srcSinkPair.value().first);
+        ASSERT_NO_FATAL_FAILURE(srcPortConfig.SetUp(module.get()));
+        WithAudioPortConfig sinkPortConfig(srcSinkPair.value().second);
+        ASSERT_NO_FATAL_FAILURE(sinkPortConfig.SetUp(module.get()));
+        {  // Check that the pair can actually be used for setting up a patch.
+            WithAudioPatch patch(srcPortConfig.get(), sinkPortConfig.get());
+            ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+        }
+        EXPECT_NO_FATAL_FAILURE(
+                SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()}));
+        EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
+                Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()},
+                {sinkPortConfig.getId()}));
+        EXPECT_NO_FATAL_FAILURE(
+                SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {}));
+        EXPECT_NO_FATAL_FAILURE(
+                SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()},
+                                      {sinkPortConfig.getId(), sinkPortConfig.getId()}));
+
+        std::set<int32_t> portConfigIds;
+        ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+        for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+            EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
+                    Status::EX_ILLEGAL_ARGUMENT, {portConfigId}, {sinkPortConfig.getId()}));
+            EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT,
+                                                          {srcPortConfig.getId()}, {portConfigId}));
+        }
+    }
+
+    void SetNonRoutablePatch(bool isInput) {
+        auto srcSinkPair = moduleConfig->getNonRoutableSrcSinkPair(isInput);
+        if (!srcSinkPair.has_value()) {
+            GTEST_SKIP() << "All possible source/sink pairs are routable";
+        }
+        WithAudioPatch patch(srcSinkPair.value().first, srcSinkPair.value().second);
+        ASSERT_NO_FATAL_FAILURE(patch.SetUpPortConfigs(module.get()));
+        Status status = patch.SetUpNoChecks(module.get());
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << ": when setting up a patch from "
+                << srcSinkPair.value().first.toString() << " to "
+                << srcSinkPair.value().second.toString() << " that does not have a route";
+    }
+
+    void SetPatch(bool isInput) {
+        auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+        if (srcSinkGroups.empty()) {
+            GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+        }
+        for (const auto& srcSinkGroup : srcSinkGroups) {
+            const auto& route = srcSinkGroup.first;
+            std::vector<WithAudioPatch> patches;
+            for (const auto& srcSink : srcSinkGroup.second) {
+                if (!route.isExclusive) {
+                    patches.emplace_back(srcSink.first, srcSink.second);
+                    EXPECT_NO_FATAL_FAILURE(patches[patches.size() - 1].SetUp(module.get()));
+                } else {
+                    WithAudioPatch patch(srcSink.first, srcSink.second);
+                    EXPECT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+                }
+            }
+        }
+    }
+
+    void UpdatePatch(bool isInput) {
+        auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+        if (srcSinkGroups.empty()) {
+            GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+        }
+        for (const auto& srcSinkGroup : srcSinkGroups) {
+            for (const auto& srcSink : srcSinkGroup.second) {
+                WithAudioPatch patch(srcSink.first, srcSink.second);
+                ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+                AudioPatch ignored;
+                EXPECT_NO_FATAL_FAILURE(module->setAudioPatch(patch.get(), &ignored));
+            }
+        }
+    }
+
+    void UpdateInvalidPatchId(bool isInput) {
+        auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+        if (srcSinkGroups.empty()) {
+            GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+        }
+        // First, set up a patch to ensure that its settings are accepted.
+        auto srcSinkGroup = *srcSinkGroups.begin();
+        auto srcSink = *srcSinkGroup.second.begin();
+        WithAudioPatch patch(srcSink.first, srcSink.second);
+        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+        // Then use the same patch setting, except for having an invalid ID.
+        std::set<int32_t> patchIds;
+        ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
+        for (const auto patchId : getNonExistentIds(patchIds)) {
+            AudioPatch patchWithNonExistendId = patch.get();
+            patchWithNonExistendId.id = patchId;
+            Status status = module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId);
+            EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                    << status << " returned for patch ID " << patchId;
+        }
+    }
+};
+
+// Not all tests require both directions, so parametrization would require
+// more abstractions.
+#define TEST_PATCH_BOTH_DIRECTIONS(method_name)                                                  \
+    TEST_P(AudioModulePatch, method_name##Input) { ASSERT_NO_FATAL_FAILURE(method_name(true)); } \
+    TEST_P(AudioModulePatch, method_name##Output) { ASSERT_NO_FATAL_FAILURE(method_name(false)); }
+
+TEST_PATCH_BOTH_DIRECTIONS(ResetPortConfigUsedByPatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetInvalidPatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetNonRoutablePatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetPatch);
+TEST_PATCH_BOTH_DIRECTIONS(UpdateInvalidPatchId);
+TEST_PATCH_BOTH_DIRECTIONS(UpdatePatch);
+
+TEST_P(AudioModulePatch, ResetInvalidPatchId) {
+    std::set<int32_t> patchIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
+    for (const auto patchId : getNonExistentIds(patchIds)) {
+        Status status = module->resetAudioPatch(patchId);
+        EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+                << status << " returned for patch ID " << patchId;
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioCoreModuleTest, AudioCoreModule,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreModule);
+INSTANTIATE_TEST_SUITE_P(AudioStreamInTest, AudioStreamIn,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIn);
+INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
+INSTANTIATE_TEST_SUITE_P(AudioPatchTest, AudioModulePatch,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModulePatch);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ProcessState::self()->setThreadPoolMaxThreadCount(1);
+    ProcessState::self()->startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/automotive/audiocontrol/aidl/default/Android.bp b/automotive/audiocontrol/aidl/default/Android.bp
index 1439cce..6bf4b9d 100644
--- a/automotive/audiocontrol/aidl/default/Android.bp
+++ b/automotive/audiocontrol/aidl/default/Android.bp
@@ -29,8 +29,9 @@
     vendor: true,
     shared_libs: [
         "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common-V1-ndk",
         "android.frameworks.automotive.powerpolicy-V1-ndk",
-        "android.hardware.automotive.audiocontrol-V1-ndk",
+        "android.hardware.automotive.audiocontrol-V2-ndk",
         "libbase",
         "libbinder_ndk",
         "libcutils",
diff --git a/automotive/audiocontrol/aidl/default/AudioControl.cpp b/automotive/audiocontrol/aidl/default/AudioControl.cpp
index c0bc796..a121f8b 100644
--- a/automotive/audiocontrol/aidl/default/AudioControl.cpp
+++ b/automotive/audiocontrol/aidl/default/AudioControl.cpp
@@ -30,6 +30,8 @@
 #include <android_audio_policy_configuration_V7_0-enums.h>
 #include <private/android_filesystem_config.h>
 
+#include <numeric>
+
 #include <stdio.h>
 
 namespace aidl::android::hardware::automotive::audiocontrol {
@@ -147,6 +149,47 @@
     return ndk::ScopedAStatus::ok();
 }
 
+template <typename aidl_type>
+static inline std::string toString(const std::vector<aidl_type>& in_values) {
+    return std::accumulate(std::begin(in_values), std::end(in_values), std::string{},
+                           [](std::string& ls, const aidl_type& rs) {
+                               return ls += (ls.empty() ? "" : ",") + rs.toString();
+                           });
+}
+template <typename aidl_enum_type>
+static inline std::string toEnumString(const std::vector<aidl_enum_type>& in_values) {
+    return std::accumulate(std::begin(in_values), std::end(in_values), std::string{},
+                           [](std::string& ls, const aidl_enum_type& rs) {
+                               return ls += (ls.empty() ? "" : ",") + toString(rs);
+                           });
+}
+
+ndk::ScopedAStatus AudioControl::onAudioFocusChangeWithMetaData(
+        const audiohalcommon::PlaybackTrackMetadata& in_playbackMetaData, int32_t in_zoneId,
+        AudioFocusChange in_focusChange) {
+    LOG(INFO) << "Focus changed: " << toString(in_focusChange).c_str() << " for metadata "
+              << in_playbackMetaData.toString().c_str() << " in zone " << in_zoneId;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus AudioControl::setAudioDeviceGainsChanged(
+        const std::vector<Reasons>& in_reasons, const std::vector<AudioGainConfigInfo>& in_gains) {
+    LOG(INFO) << "Audio Device Gains changed: resons:" << toEnumString(in_reasons).c_str()
+              << " for devices: " << toString(in_gains).c_str();
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus AudioControl::registerGainCallback(
+        const std::shared_ptr<IAudioGainCallback>& in_callback) {
+    LOG(DEBUG) << ": " << __func__;
+    if (in_callback) {
+        std::atomic_store(&mAudioGainCallback, in_callback);
+    } else {
+        LOG(ERROR) << "Unexpected nullptr for audio gain callback resulting in no-op.";
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
 binder_status_t AudioControl::dump(int fd, const char** args, uint32_t numArgs) {
     if (numArgs == 0) {
         return dumpsys(fd);
@@ -159,6 +202,12 @@
         return cmdRequestFocus(fd, args, numArgs);
     } else if (EqualsIgnoreCase(option, "--abandon")) {
         return cmdAbandonFocus(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--requestFocusWithMetaData")) {
+        return cmdRequestFocusWithMetaData(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--abandonFocusWithMetaData")) {
+        return cmdAbandonFocusWithMetaData(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--audioGainCallback")) {
+        return cmdOnAudioDeviceGainsChanged(fd, args, numArgs);
     } else {
         dprintf(fd, "Invalid option: %s\n", option.c_str());
         return STATUS_BAD_VALUE;
@@ -171,20 +220,49 @@
     } else {
         dprintf(fd, "Focus listener registered\n");
     }
+    dprintf(fd, "AudioGainCallback %sregistered\n", (mAudioGainCallback == nullptr ? "NOT " : ""));
     return STATUS_OK;
 }
 
 binder_status_t AudioControl::cmdHelp(int fd) const {
     dprintf(fd, "Usage: \n\n");
-    dprintf(fd, "[no args]: dumps focus listener status\n");
+    dprintf(fd, "[no args]: dumps focus listener / gain callback registered status\n");
     dprintf(fd, "--help: shows this help\n");
     dprintf(fd,
             "--request <USAGE> <ZONE_ID> <FOCUS_GAIN>: requests audio focus for specified "
-            "usage (string), audio zone ID (int), and focus gain type (int)\n");
+            "usage (string), audio zone ID (int), and focus gain type (int)\n"
+            "Deprecated, use MetaData instead\n");
     dprintf(fd,
             "--abandon <USAGE> <ZONE_ID>: abandons audio focus for specified usage (string) and "
-            "audio zone ID (int)\n");
+            "audio zone ID (int)\n"
+            "Deprecated, use MetaData instead\n");
     dprintf(fd, "See audio_policy_configuration.xsd for valid AudioUsage values.\n");
+
+    dprintf(fd,
+            "--requestFocusWithMetaData <METADATA> <ZONE_ID> <FOCUS_GAIN>: "
+            "requests audio focus for specified metadata, audio zone ID (int), "
+            "and focus gain type (int)\n");
+    dprintf(fd,
+            "--abandonFocusWithMetaData <METADATA> <ZONE_ID>: "
+            "abandons audio focus for specified metadata and audio zone ID (int)\n");
+    dprintf(fd,
+            "--audioGainCallback <ZONE_ID> <REASON_1>[,<REASON_N> ...]"
+            "<DEVICE_ADDRESS_1> <GAIN_INDEX_1> [<DEVICE_ADDRESS_N> <GAIN_INDEX_N> ...]: fire audio "
+            "gain callback for audio zone ID (int), the given reasons (csv int) for given pairs "
+            "of device address (string) and gain index (int) \n");
+
+    dprintf(fd,
+            "Note on <METADATA>: <USAGE,CONTENT_TYPE[,TAGS]>  specified as where (int)usage, "
+            "(int)content type and tags (string)string)\n");
+    dprintf(fd,
+            "See android/media/audio/common/AudioUsageType.aidl for valid AudioUsage values.\n");
+    dprintf(fd,
+            "See android/media/audio/common/AudioContentType.aidl for valid AudioContentType "
+            "values.\n");
+    dprintf(fd,
+            "Tags are optional. If provided, it must follow the <key>=<value> pattern, where the "
+            "value is namespaced (for example com.google.strategy=VR).\n");
+
     return STATUS_OK;
 }
 
@@ -266,4 +344,174 @@
     return STATUS_OK;
 }
 
+binder_status_t AudioControl::parseMetaData(int fd, const std::string& metadataLiteral,
+                                            audiohalcommon::PlaybackTrackMetadata& trackMetadata) {
+    std::stringstream csvMetaData(metadataLiteral);
+    std::vector<std::string> splitMetaData;
+    std::string attribute;
+    while (getline(csvMetaData, attribute, ',')) {
+        splitMetaData.push_back(attribute);
+    }
+    if (splitMetaData.size() != 2 && splitMetaData.size() != 3) {
+        dprintf(fd,
+                "Invalid metadata: %s, please provide <METADATA> as <USAGE,CONTENT_TYPE[,TAGS]> "
+                "where (int)usage, (int)content type and tags (string)string)\n",
+                metadataLiteral.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    int usage;
+    if (!safelyParseInt(splitMetaData[0], &usage)) {
+        dprintf(fd, "Non-integer usage provided with request: %s\n", splitMetaData[0].c_str());
+        return STATUS_BAD_VALUE;
+    }
+    int contentType;
+    if (!safelyParseInt(splitMetaData[1], &contentType)) {
+        dprintf(fd, "Non-integer content type provided with request: %s\n",
+                splitMetaData[1].c_str());
+        return STATUS_BAD_VALUE;
+    }
+    const std::string tags = (splitMetaData.size() == 3 ? splitMetaData[2] : "");
+
+    trackMetadata = {.usage = static_cast<audiomediacommon::AudioUsage>(usage),
+                     .contentType = static_cast<audiomediacommon::AudioContentType>(contentType),
+                     .tags = {tags}};
+    return STATUS_OK;
+}
+
+binder_status_t AudioControl::cmdRequestFocusWithMetaData(int fd, const char** args,
+                                                          uint32_t numArgs) {
+    if (!checkCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 4) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide:\n"
+                "--requestFocusWithMetaData <METADATA> <ZONE_ID> <FOCUS_GAIN>: "
+                "requests audio focus for specified metadata, audio zone ID (int), "
+                "and focus gain type (int)\n");
+        return STATUS_BAD_VALUE;
+    }
+    std::string metadataLiteral = std::string(args[1]);
+    audiohalcommon::PlaybackTrackMetadata trackMetadata{};
+    auto status = parseMetaData(fd, metadataLiteral, trackMetadata);
+    if (status != STATUS_OK) {
+        return status;
+    }
+
+    int zoneId;
+    if (!safelyParseInt(std::string(args[2]), &zoneId)) {
+        dprintf(fd, "Non-integer zoneId provided with request: %s\n", std::string(args[2]).c_str());
+        return STATUS_BAD_VALUE;
+    }
+
+    int focusGainValue;
+    if (!safelyParseInt(std::string(args[3]), &focusGainValue)) {
+        dprintf(fd, "Non-integer focusGain provided with request: %s\n",
+                std::string(args[3]).c_str());
+        return STATUS_BAD_VALUE;
+    }
+    AudioFocusChange focusGain = AudioFocusChange(focusGainValue);
+
+    if (mFocusListener == nullptr) {
+        dprintf(fd, "Unable to request focus - no focus listener registered\n");
+        return STATUS_BAD_VALUE;
+    }
+    mFocusListener->requestAudioFocusWithMetaData(trackMetadata, zoneId, focusGain);
+    dprintf(fd, "Requested focus for metadata %s, zoneId %d, and focusGain %d\n",
+            trackMetadata.toString().c_str(), zoneId, focusGain);
+    return STATUS_OK;
+}
+
+binder_status_t AudioControl::cmdAbandonFocusWithMetaData(int fd, const char** args,
+                                                          uint32_t numArgs) {
+    if (!checkCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 3) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide:\n"
+                "--abandonFocusWithMetaData <METADATA> <ZONE_ID>: "
+                "abandons audio focus for specified metadata and audio zone ID (int)\n");
+        return STATUS_BAD_VALUE;
+    }
+    std::string metadataLiteral = std::string(args[1]);
+    audiohalcommon::PlaybackTrackMetadata trackMetadata{};
+    auto status = parseMetaData(fd, metadataLiteral, trackMetadata);
+    if (status != STATUS_OK) {
+        return status;
+    }
+    int zoneId;
+    if (!safelyParseInt(std::string(args[2]), &zoneId)) {
+        dprintf(fd, "Non-integer zoneId provided with request: %s\n", std::string(args[2]).c_str());
+        return STATUS_BAD_VALUE;
+    }
+    if (mFocusListener == nullptr) {
+        dprintf(fd, "Unable to abandon focus - no focus listener registered\n");
+        return STATUS_BAD_VALUE;
+    }
+
+    mFocusListener->abandonAudioFocusWithMetaData(trackMetadata, zoneId);
+    dprintf(fd, "Abandoned focus for metadata %s and zoneId %d\n", trackMetadata.toString().c_str(),
+            zoneId);
+    return STATUS_OK;
+}
+
+binder_status_t AudioControl::cmdOnAudioDeviceGainsChanged(int fd, const char** args,
+                                                           uint32_t numArgs) {
+    if (!checkCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if ((numArgs + 1) % 2 != 0) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide\n"
+                "--audioGainCallback <ZONE_ID> <REASON_1>[,<REASON_N> ...]"
+                "<DEVICE_ADDRESS_1> <GAIN_INDEX_1> [<DEVICE_ADDRESS_N> <GAIN_INDEX_N> ...]: "
+                "fire audio gain callback for audio zone ID (int), "
+                "with the given reasons (csv int) for given pairs of device address (string) "
+                "and gain index (int) \n");
+        return STATUS_BAD_VALUE;
+    }
+    int zoneId;
+    if (!safelyParseInt(string(args[1]), &zoneId)) {
+        dprintf(fd, "Non-integer zoneId provided with request: %s\n", std::string(args[1]).c_str());
+        return STATUS_BAD_VALUE;
+    }
+    std::string reasonsLiteral = std::string(args[2]);
+    std::stringstream csvReasonsLiteral(reasonsLiteral);
+    std::vector<Reasons> reasons;
+    std::string reasonLiteral;
+    while (getline(csvReasonsLiteral, reasonLiteral, ',')) {
+        int reason;
+        if (!safelyParseInt(reasonLiteral, &reason)) {
+            dprintf(fd, "Invalid Reason(s) provided %s\n", reasonLiteral.c_str());
+            return STATUS_BAD_VALUE;
+        }
+        reasons.push_back(static_cast<Reasons>(reason));
+    }
+
+    std::vector<AudioGainConfigInfo> agcis{};
+    for (uint32_t i = 3; i < numArgs; i += 2) {
+        std::string deviceAddress = std::string(args[i]);
+        int32_t index;
+        if (!safelyParseInt(std::string(args[i + 1]), &index)) {
+            dprintf(fd, "Non-integer index provided with request: %s\n",
+                    std::string(args[i + 1]).c_str());
+            return STATUS_BAD_VALUE;
+        }
+        AudioGainConfigInfo agci{zoneId, deviceAddress, index};
+        agcis.push_back(agci);
+    }
+    if (mAudioGainCallback == nullptr) {
+        dprintf(fd,
+                "Unable to trig audio gain callback for reasons=%s and gains=%s\n"
+                " - no audio gain callback registered\n",
+                toEnumString(reasons).c_str(), toString(agcis).c_str());
+        return STATUS_BAD_VALUE;
+    }
+
+    mAudioGainCallback->onAudioDeviceGainsChanged(reasons, agcis);
+    dprintf(fd, "Fired audio gain callback for reasons=%s and gains=%s\n",
+            toEnumString(reasons).c_str(), toString(agcis).c_str());
+    return STATUS_OK;
+}
 }  // namespace aidl::android::hardware::automotive::audiocontrol
diff --git a/automotive/audiocontrol/aidl/default/AudioControl.h b/automotive/audiocontrol/aidl/default/AudioControl.h
index cca9c44..16b80e8 100644
--- a/automotive/audiocontrol/aidl/default/AudioControl.h
+++ b/automotive/audiocontrol/aidl/default/AudioControl.h
@@ -17,12 +17,20 @@
 #define ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_AUDIOCONTROL_H
 
 #include <aidl/android/hardware/automotive/audiocontrol/AudioFocusChange.h>
+#include <aidl/android/hardware/automotive/audiocontrol/AudioGainConfigInfo.h>
 #include <aidl/android/hardware/automotive/audiocontrol/BnAudioControl.h>
 #include <aidl/android/hardware/automotive/audiocontrol/DuckingInfo.h>
+#include <aidl/android/hardware/automotive/audiocontrol/IAudioGainCallback.h>
 #include <aidl/android/hardware/automotive/audiocontrol/MutingInfo.h>
+#include <aidl/android/hardware/automotive/audiocontrol/Reasons.h>
+
+#include <aidl/android/hardware/audio/common/PlaybackTrackMetadata.h>
 
 namespace aidl::android::hardware::automotive::audiocontrol {
 
+namespace audiohalcommon = ::aidl::android::hardware::audio::common;
+namespace audiomediacommon = ::aidl::android::media::audio::common;
+
 class AudioControl : public BnAudioControl {
   public:
     ndk::ScopedAStatus onAudioFocusChange(const std::string& in_usage, int32_t in_zoneId,
@@ -35,6 +43,15 @@
             const std::shared_ptr<IFocusListener>& in_listener) override;
     ndk::ScopedAStatus setBalanceTowardRight(float in_value) override;
     ndk::ScopedAStatus setFadeTowardFront(float in_value) override;
+    ndk::ScopedAStatus onAudioFocusChangeWithMetaData(
+            const audiohalcommon::PlaybackTrackMetadata& in_playbackMetaData, int32_t in_zoneId,
+            AudioFocusChange in_focusChange) override;
+    ndk::ScopedAStatus setAudioDeviceGainsChanged(
+            const std::vector<Reasons>& in_reasons,
+            const std::vector<AudioGainConfigInfo>& in_gains) override;
+    ndk::ScopedAStatus registerGainCallback(
+            const std::shared_ptr<IAudioGainCallback>& in_callback) override;
+
     binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
 
   private:
@@ -44,9 +61,22 @@
     // listener, then it should also include mutexes or make the listener atomic.
     std::shared_ptr<IFocusListener> mFocusListener;
 
+    /**
+     * @brief mAudioGainCallback will be used by this HAL instance to communicate e.g. with a single
+     * instance of CarAudioService to report unexpected gain changed.
+     */
+    std::shared_ptr<IAudioGainCallback> mAudioGainCallback = nullptr;
+
     binder_status_t cmdHelp(int fd) const;
     binder_status_t cmdRequestFocus(int fd, const char** args, uint32_t numArgs);
     binder_status_t cmdAbandonFocus(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdRequestFocusWithMetaData(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdAbandonFocusWithMetaData(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdOnAudioDeviceGainsChanged(int fd, const char** args, uint32_t numArgs);
+
+    binder_status_t parseMetaData(int fd, const std::string& metadataLiteral,
+                                  audiohalcommon::PlaybackTrackMetadata& trackMetadata);
+
     binder_status_t dumpsys(int fd);
 };
 
diff --git a/automotive/audiocontrol/aidl/default/audiocontrol-default.xml b/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
index f95d05f..e82f6fa 100644
--- a/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
+++ b/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
@@ -1,4 +1,4 @@
-<manifest version="1.0" type="device">
+<manifest version="2.0" type="device">
     <hal format="aidl">
         <name>android.hardware.automotive.audiocontrol</name>
         <fqname>IAudioControl/default</fqname>
diff --git a/automotive/audiocontrol/aidl/vts/Android.bp b/automotive/audiocontrol/aidl/vts/Android.bp
index 6856b91..3d4be48 100644
--- a/automotive/audiocontrol/aidl/vts/Android.bp
+++ b/automotive/audiocontrol/aidl/vts/Android.bp
@@ -37,11 +37,16 @@
         "libxml2",
     ],
     static_libs: [
-        "android.hardware.automotive.audiocontrol-V1-cpp",
+        "android.hardware.automotive.audiocontrol-V2-cpp",
+        "android.hardware.audio.common-V1-cpp",
+        "android.media.audio.common.types-V1-cpp",
         "libgmock",
     ],
     test_suites: [
         "general-tests",
         "vts",
     ],
+    cflags: [
+        "-Wno-deprecated-declarations",
+    ],
 }
diff --git a/automotive/audiocontrol/aidl/vts/VtsHalAudioControlTargetTest.cpp b/automotive/audiocontrol/aidl/vts/VtsHalAudioControlTargetTest.cpp
index ae53c68..f4f5eef 100644
--- a/automotive/audiocontrol/aidl/vts/VtsHalAudioControlTargetTest.cpp
+++ b/automotive/audiocontrol/aidl/vts/VtsHalAudioControlTargetTest.cpp
@@ -19,6 +19,7 @@
 #include <aidl/Vintf.h>
 #include <gmock/gmock.h>
 
+#include <android/hardware/automotive/audiocontrol/BnAudioGainCallback.h>
 #include <android/hardware/automotive/audiocontrol/BnFocusListener.h>
 #include <android/hardware/automotive/audiocontrol/IAudioControl.h>
 #include <android/log.h>
@@ -30,10 +31,13 @@
 using android::String16;
 using android::binder::Status;
 using android::hardware::automotive::audiocontrol::AudioFocusChange;
+using android::hardware::automotive::audiocontrol::AudioGainConfigInfo;
+using android::hardware::automotive::audiocontrol::BnAudioGainCallback;
 using android::hardware::automotive::audiocontrol::BnFocusListener;
 using android::hardware::automotive::audiocontrol::DuckingInfo;
 using android::hardware::automotive::audiocontrol::IAudioControl;
 using android::hardware::automotive::audiocontrol::MutingInfo;
+using android::hardware::automotive::audiocontrol::Reasons;
 
 #include "android_audio_policy_configuration_V7_0.h"
 
@@ -41,6 +45,9 @@
 using namespace android::audio::policy::configuration::V7_0;
 }
 
+namespace audiohalcommon = android::hardware::audio::common;
+namespace audiomediacommon = android::media::audio::common;
+
 class AudioControlAidl : public testing::TestWithParam<std::string> {
   public:
     virtual void SetUp() override {
@@ -103,6 +110,11 @@
     MOCK_METHOD(Status, requestAudioFocus,
                 (const String16& usage, int32_t zoneId, AudioFocusChange focusGain));
     MOCK_METHOD(Status, abandonAudioFocus, (const String16& usage, int32_t zoneId));
+    MOCK_METHOD(Status, requestAudioFocusWithMetaData,
+                (const audiohalcommon::PlaybackTrackMetadata& metaData, int32_t zoneId,
+                 AudioFocusChange focusGain));
+    MOCK_METHOD(Status, abandonAudioFocusWithMetaData,
+                (const audiohalcommon::PlaybackTrackMetadata& metaData, int32_t zoneId));
 };
 
 /*
@@ -159,6 +171,61 @@
     ASSERT_TRUE(audioControl->onDevicesToDuckChange(duckingInfos).isOk());
 }
 
+TEST_P(AudioControlAidl, FocusChangeWithMetaDataExercise) {
+    ALOGI("Focus Change test");
+
+    audiohalcommon::PlaybackTrackMetadata metadata;
+    metadata.usage = audiomediacommon::AudioUsage::MEDIA;
+    metadata.contentType = audiomediacommon::AudioContentType::MUSIC;
+    metadata.tags = {"com.google.android=VR"};
+    ASSERT_TRUE(
+            audioControl
+                    ->onAudioFocusChangeWithMetaData(metadata, 0, AudioFocusChange::GAIN_TRANSIENT)
+                    .isOk());
+};
+
+TEST_P(AudioControlAidl, SetAudioDeviceGainsChangedExercise) {
+    ALOGI("Set Audio Gains Changed test");
+
+    const std::vector<Reasons> reasons{Reasons::FORCED_MASTER_MUTE, Reasons::NAV_DUCKING};
+    AudioGainConfigInfo agci1;
+    agci1.zoneId = 0;
+    agci1.devicePortAddress = String16("address 1");
+    agci1.volumeIndex = 8;
+
+    AudioGainConfigInfo agci2;
+    agci1.zoneId = 0;
+    agci1.devicePortAddress = String16("address 2");
+    agci1.volumeIndex = 1;
+
+    std::vector<AudioGainConfigInfo> gains{agci1, agci2};
+    ASSERT_TRUE(audioControl->setAudioDeviceGainsChanged(reasons, gains).isOk());
+}
+
+/*
+ * Test Audio Gain Callback registration.
+ *
+ * Verifies that:
+ * - registerGainCallback succeeds;
+ * - registering a second callback succeeds in replacing the first;
+ * - closing handle does not crash;
+ */
+struct AudioGainCallbackMock : BnAudioGainCallback {
+    MOCK_METHOD(Status, onAudioDeviceGainsChanged,
+                (const std::vector<Reasons>& reasons,
+                 const std::vector<AudioGainConfigInfo>& gains));
+};
+
+TEST_P(AudioControlAidl, AudioGainCallbackRegistration) {
+    ALOGI("Focus listener test");
+
+    sp<AudioGainCallbackMock> gainCallback = new AudioGainCallbackMock();
+    ASSERT_TRUE(audioControl->registerGainCallback(gainCallback).isOk());
+
+    sp<AudioGainCallbackMock> gainCallback2 = new AudioGainCallbackMock();
+    ASSERT_TRUE(audioControl->registerGainCallback(gainCallback2).isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioControlAidl);
 INSTANTIATE_TEST_SUITE_P(
         Audiocontrol, AudioControlAidl,
diff --git a/automotive/can/1.0/default/libnl++/Socket.cpp b/automotive/can/1.0/default/libnl++/Socket.cpp
index cc1d839..221063d 100644
--- a/automotive/can/1.0/default/libnl++/Socket.cpp
+++ b/automotive/can/1.0/default/libnl++/Socket.cpp
@@ -47,6 +47,17 @@
     }
 }
 
+void Socket::clearPollErr() {
+    sockaddr_nl sa = {};
+    socklen_t saLen = sizeof(sa);
+    const auto bytesReceived = recvfrom(mFd.get(), mReceiveBuffer.data(), mReceiveBuffer.size(), 0,
+                                        reinterpret_cast<sockaddr*>(&sa), &saLen);
+    if (errno != EINVAL) {
+        PLOG(WARNING) << "clearPollError() caught unexpected error: ";
+    }
+    CHECK_LE(bytesReceived, 0) << "clearPollError() didn't find an error!";
+}
+
 bool Socket::send(const Buffer<nlmsghdr>& msg, const sockaddr_nl& sa) {
     if constexpr (kSuperVerbose) {
         LOG(VERBOSE) << (mFailed ? "(not) " : "") << "sending to " << sa.nl_pid << ": "
@@ -110,6 +121,13 @@
     if constexpr (kSuperVerbose) {
         LOG(VERBOSE) << "received from " << sa.nl_pid << ": " << toString(msg, mProtocol);
     }
+    long headerByteTotal = 0;
+    for (const auto hdr : msg) {
+        headerByteTotal += hdr->nlmsg_len;
+    }
+    if (bytesReceived != headerByteTotal) {
+        LOG(ERROR) << "received " << bytesReceived << " bytes, header claims " << headerByteTotal;
+    }
     return {msg, sa};
 }
 
@@ -159,6 +177,7 @@
 }
 
 pollfd Socket::preparePoll(short events) {
+    CHECK(mFd.get() > 0) << "Netlink socket fd is invalid!";
     return {mFd.get(), events, 0};
 }
 
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
index 7ec0f7b..996a350 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
@@ -55,6 +55,12 @@
     Socket(int protocol, unsigned pid = 0, uint32_t groups = 0);
 
     /**
+     * Attempt to clear POLLERR by recv-ing.
+     * TODO(224850481): determine if this is necessary, or if the socket is locked up anyway.
+     */
+    void clearPollErr();
+
+    /**
      * Send Netlink message with incremented sequence number to the Kernel.
      *
      * \param msg Message to send. Its sequence number will be updated.
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
index 900560e..3ad101e 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
@@ -30,6 +30,7 @@
     const auto familyName = msg.attributes.get<std::string>(CTRL_ATTR_FAMILY_NAME);
     const auto familyId = msg.attributes.get<uint16_t>(CTRL_ATTR_FAMILY_ID);
 
+    // TODO(224845900): NETLINK_GENERIC == 16, and (erroneously?) sets off this warning
     if (familyId < GENL_START_ALLOC) {
         LOG(WARNING) << "Invalid family ID: " << familyId;
         return true;
diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp
index 4c08ef3..4172e63 100644
--- a/automotive/evs/1.1/default/Android.bp
+++ b/automotive/evs/1.1/default/Android.bp
@@ -13,6 +13,7 @@
     proprietary: true,
     relative_install_path: "hw",
     srcs: [
+        ":libgui_frame_event_aidl",
         "*.cpp",
     ],
     init_rc: ["android.hardware.automotive.evs@1.1-service.rc"],
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index cfbbbd3..9edd2bd 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -374,6 +374,15 @@
 
         {.config =
                  {
+                         .prop = toInt(VehicleProperty::FUEL_VOLUME_DISPLAY_UNITS),
+                         .access = VehiclePropertyAccess::READ_WRITE,
+                         .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+                         .configArray = {(int)VehicleUnit::LITER, (int)VehicleUnit::US_GALLON},
+                 },
+         .initialValue = {.int32Values = {(int)VehicleUnit::LITER}}},
+
+        {.config =
+                 {
                          .prop = toInt(VehicleProperty::HW_KEY_INPUT),
                          .access = VehiclePropertyAccess::READ,
                          .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
index e3c8dd6..d35792d 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
@@ -141,7 +141,7 @@
 TEST_F(DefaultVhalImplTest, testListProperties) {
     std::vector<VehiclePropConfig> configs = mHal->listProperties();
 
-    EXPECT_EQ((size_t)121, configs.size());
+    EXPECT_EQ((size_t)122, configs.size());
 }
 
 TEST_F(DefaultVhalImplTest, testGetDefaultPropertyFloat) {
diff --git a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
index fda0da9..63797b4 100644
--- a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
+++ b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
@@ -453,6 +453,15 @@
 
         {.config =
                  {
+                         .prop = toInt(VehicleProperty::FUEL_VOLUME_DISPLAY_UNITS),
+                         .access = VehiclePropertyAccess::READ_WRITE,
+                         .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+                         .configArray = {(int)VehicleUnit::LITER, (int)VehicleUnit::US_GALLON},
+                 },
+         .initialValue = {.int32Values = {(int)VehicleUnit::LITER}}},
+
+        {.config =
+                 {
                          .prop = toInt(VehicleProperty::HW_KEY_INPUT),
                          .access = VehiclePropertyAccess::READ,
                          .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 8b3830a..ff84558 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -26,6 +26,18 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true">
+        <name>android.hardware.audio.core</name>
+        <version>1</version>
+        <interface>
+            <name>IModule</name>
+            <instance>default</instance>
+        </interface>
+        <interface>
+            <name>IConfig</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
          <name>android.hardware.authsecret</name>
          <version>1</version>
          <interface>
diff --git a/tv/tuner/aidl/default/tuner-default.rc b/tv/tuner/aidl/default/tuner-default.rc
index d0248c2..661c219 100644
--- a/tv/tuner/aidl/default/tuner-default.rc
+++ b/tv/tuner/aidl/default/tuner-default.rc
@@ -3,5 +3,5 @@
     user media
     group mediadrm drmrpc
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks
+    task_profiles ProcessCapacityHigh
     onrestart restart media.tuner
diff --git a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
index 00cb8e0..6ee5799 100644
--- a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
+++ b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
@@ -71,8 +71,8 @@
      * The UCI message format is as per UCI  protocol and it is
      * defined in "FiRa Consortium - UCI Generic Specification_v1.0" specification at FiRa
      * consortium.
-     * WIP doc link: https://groups.firaconsortium.org/wg/Technical/document/folder/127.
-     * TODO(b/196004116): Link to the published specification.
+     *
+     * UCI 1.1 specification: https://groups.firaconsortium.org/wg/members/document/1949.
      *
      * This method may queue writes and return immediately, or it may block until data is written.
      * Implementation must guarantee that writes are executed in order.
diff --git a/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl b/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
index 75853cd..f31aeba 100755
--- a/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
+++ b/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
@@ -28,8 +28,7 @@
      * can use to pass incoming data to the stack.  These include UCI
      * responses and notifications from the UWB subsystem.
      *
-     * WIP doc link: https://groups.firaconsortium.org/wg/Technical/document/folder/127.
-     * TODO(b/196004116): Link to the published specification.
+     * UCI 1.1 specification: https://groups.firaconsortium.org/wg/members/document/1949.
      *
      * @param data UCI packet sent.
      */
diff --git a/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
index ac8653e..32e5a6e 100644
--- a/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
+++ b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
@@ -117,14 +117,13 @@
     int clientSocketFd, const char* clientName,
     struct android_nlinterceptor_InterceptedSocket* interceptedSocket);
 
-void android_nlinterceptor_closeSocket(
-    const struct android_nlinterceptor_InterceptedSocket* sock);
+void android_nlinterceptor_closeSocket(struct android_nlinterceptor_InterceptedSocket sock);
 
-bool android_nlinterceptor_subscribe(
-    const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group);
+bool android_nlinterceptor_subscribe(struct android_nlinterceptor_InterceptedSocket sock,
+                                     uint32_t group);
 
-bool android_nlinterceptor_unsubscribe(
-    const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group);
+bool android_nlinterceptor_unsubscribe(struct android_nlinterceptor_InterceptedSocket sock,
+                                       uint32_t group);
 
 #ifdef __cplusplus
 }
diff --git a/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
index 575f900..aae7a3a 100644
--- a/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
+++ b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
@@ -150,25 +150,18 @@
     return true;
 }
 
-extern "C" void android_nlinterceptor_closeSocket(
-    const android_nlinterceptor_InterceptedSocket* sock) {
-    if (!sock) {
-        LOG(ERROR) << "Can't close socket identified by a null pointer!";
-        return;
-    }
-    closeSocket({sock->nlFamily, sock->portId});
+extern "C" void android_nlinterceptor_closeSocket(android_nlinterceptor_InterceptedSocket sock) {
+    closeSocket({sock.nlFamily, sock.portId});
 }
 
-extern "C" bool android_nlinterceptor_subscribe(
-    const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) {
-    if (!sock) return false;
-    return subscribe({sock->nlFamily, sock->portId}, group);
+extern "C" bool android_nlinterceptor_subscribe(android_nlinterceptor_InterceptedSocket sock,
+                                                uint32_t group) {
+    return subscribe({sock.nlFamily, sock.portId}, group);
 }
 
-extern "C" bool android_nlinterceptor_unsubscribe(
-    const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) {
-    if (!sock) return false;
-    return unsubscribe({sock->nlFamily, sock->portId}, group);
+extern "C" bool android_nlinterceptor_unsubscribe(android_nlinterceptor_InterceptedSocket sock,
+                                                  uint32_t group) {
+    return unsubscribe({sock.nlFamily, sock.portId}, group);
 }
 
 }  // namespace android::nlinterceptor