Merge "Add a wrapper for callers of BootControl HAL" am: ced9514d25 am: 7fb759e5ed am: 384b5c2536 am: 87edb82d5c am: 96008d00df

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

Change-Id: I97deaf614c01dde78dc3576f0ef65ada98d9b658
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 fd893ec..7db50d5 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -64,3 +64,31 @@
     ],
 
 }
+
+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",
+        "android/hardware/audio/core/ModuleDebug.aidl",
+    ],
+    imports: [
+        "android.hardware.audio.common-V1",
+        "android.media.audio.common.types-V1",
+    ],
+    stability: "vintf",
+    backend: {
+        // The C++ backend is disabled transitively due to use of FMQ.
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            platform_apis: true,
+        },
+    },
+}
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..f8bc2c7
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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 {
+  void setModuleDebug(in android.hardware.audio.core.ModuleDebug debug);
+  android.media.audio.common.AudioPort connectExternalDevice(in android.media.audio.common.AudioPort templateIdAndAdditionalData);
+  void disconnectExternalDevice(int portId);
+  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.AudioRoute[] getAudioRoutesForAudioPort(int portId);
+  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/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..80ee185
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 ModuleDebug {
+  boolean simulateDeviceConnections;
+}
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..802cb2f
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,408 @@
+/*
+ * 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.hardware.audio.core.ModuleDebug;
+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 {
+    /**
+     * Sets debugging configuration for the HAL module. This method is only
+     * called during xTS testing and is intended for validating the aspects of
+     * the HAL module behavior that would otherwise require human intervention.
+     *
+     * The HAL module must throw an error if there is an attempt to change
+     * the debug behavior for the aspect which is currently in use.
+     *
+     * @param debug The debug options.
+     * @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
+     *                          which is currently in use.
+     */
+    void setModuleDebug(in ModuleDebug debug);
+
+    /**
+     * Set a device port of an external device into connected state.
+     *
+     * This method is used to inform the HAL module that an external device has
+     * been connected to a device port selected using the 'id' field of the
+     * input AudioPort parameter. This device port must have dynamic profiles
+     * (an empty list of profiles). This port is further referenced to as "port
+     * template" because it acts as a template for creating a new instance of a
+     * "connected" device port which gets returned from this method.
+     *
+     * The input AudioPort parameter may contain any additional data obtained by
+     * the system side from other subsystems. The nature of data depends on the
+     * type of the connection. For example, for point-to-multipoint external
+     * device connections, the input parameter may contain the address of the
+     * connected external device. Another example is EDID information for HDMI
+     * connections (ExtraAudioDescriptor), which can be provided by the HDMI-CEC
+     * HAL module.
+     *
+     * It is the responsibility of the HAL module to query audio profiles
+     * supported by the connected device and return them as part of the returned
+     * AudioPort instance. In the case when the HAL is unable to query the
+     * external device, an error must be thrown.
+     *
+     * Thus, the returned audio port instance is the result of combining the
+     * following information:
+     *  - a unique port ID generated by the HAL module;
+     *  - static information from the port template;
+     *  - list of audio profiles supported by the connected device;
+     *  - additional data from the input AudioPort parameter.
+     *
+     * The HAL module must also update the list of audio routes to include the
+     * ID of the instantiated connected device port. Normally, the connected
+     * port allows the same routing as the port template.
+     *
+     * Also see notes on 'ModuleDebug.simulateDeviceConnections'.
+     *
+     * The following protocol is used by HAL module client for handling
+     * connection of an external device:
+     *  1. Obtain the list of device ports and their IDs via 'getAudioPorts'
+     *     method. Select the appropriate port template using
+     *     AudioDeviceDescription ('ext.device' field of AudioPort).
+     *  2. Combine the ID of the port template with any additional data and call
+     *     'connectExternalDevice'. The HAL module returns a new instance of
+     *     AudioPort created using the rules explained above. Both
+     *     'getAudioPort' and 'getAudioPorts' methods will be returning the same
+     *     information for this port until disconnection.
+     *  3. Configure the connected port with one of supported profiles using
+     *     'setAudioPortConfig'.
+     *  4. Query the list of AudioRoutes for the new AudioPort using
+     *     'getAudioRoutesForAudioPort' or 'getAudioRoutes' methods.
+     *
+     * External devices are distinguished by the connection type and device
+     * address. Calling this method multiple times to inform about connection of
+     * the same external device without disconnecting it first is an error.
+     *
+     * The HAL module must perform validation of the input parameter and throw
+     * an error if it is lacking required information, for example, when no
+     * device address is specified for a point-to-multipoint external device
+     * connection.
+     *
+     * Handling of a disconnect is done in a reverse order:
+     *  1. Reset port configuration using the 'resetAudioPortConfig' method.
+     *  2. Release the connected device port by calling the 'disconnectExternalDevice'
+     *     method. This also removes the audio routes associated with this
+     *     device port.
+     *
+     * @return New instance of an audio port for the connected external device.
+     * @param templateIdAndAdditionalData Specifies port template ID and any
+     *                                    additional data.
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If the template port can not be found by the ID.
+     *                             - If the template is not a device port, or
+     *                               it does not have dynamic profiles.
+     *                             - If the input parameter is lacking required
+     *                               information.
+     * @throws EX_ILLEGAL_STATE In the following cases:
+     *                          - If the HAL module is unable to query audio profiles.
+     *                          - If the external device has already been connected.
+     */
+    AudioPort connectExternalDevice(in AudioPort templateIdAndAdditionalData);
+
+    /**
+     * Set a device port of a an external device into disconnected state.
+     *
+     * This method is used to inform the HAL module that an external device has
+     * been disconnected. The 'portId' must be of a connected device port
+     * instance previously instantiated using the 'connectExternalDevice'
+     * method.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+     *                             - If the port can not be found by the ID.
+     *                             - If this is not a connected device port.
+     * @throws EX_ILLEGAL_STATE If the port has active configurations.
+     */
+    void disconnectExternalDevice(int portId);
+
+    /**
+     * 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. The values of the AudioPort structure must be the same as
+     * currently returned by the 'getAudioPorts' method. The 'getAudioPort'
+     * method is provided to reduce overhead in the case when the client needs
+     * to check the state of one port only.
+     *
+     * @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 the current state of all audio ports provided by this module.
+     *
+     * Returns a list of all mix ports and device ports provided by this HAL
+     * module, reflecting their current states. Each returned port must have a
+     * unique ID within this module ('AudioPort.id' field). The list also
+     * includes "connected" ports created using 'connectExternalDevice' method.
+     *
+     * @return The list of audio ports.
+     */
+    AudioPort[] getAudioPorts();
+
+    /**
+     * Return all current audio routes of this module.
+     *
+     * Returns the current list of audio routes, that is, allowed connections
+     * between audio ports. The list can change when new device audio ports
+     * get created as a result of connecting or disconnecting of external
+     * devices.
+     *
+     * @return The list of audio routes.
+     */
+    AudioRoute[] getAudioRoutes();
+
+    /**
+     * Return audio routes related to the specified audio port.
+     *
+     * Returns the list of audio routes that include the specified port ID
+     * as a source or as a sink. The returned list is a subset of the result
+     * returned by the 'getAudioRoutes' method, filtered by the port ID.
+     * An empty list can be returned, indicating that the audio port can not
+     * be used for creating audio patches.
+     *
+     * @return The list of audio routes.
+     * @param portId The ID of the audio port.
+     * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
+     */
+    AudioRoute[] getAudioRoutesForAudioPort(int portId);
+
+    /**
+     * 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.
+     *                             - If the offload info is not provided for an offload
+     *                               port configuration.
+     * @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.
+     *
+     * Device ports with dynamic audio profiles (an empty list of profiles)
+     * can not be used with this method. The list of profiles must be filled in
+     * as a result of calling 'connectExternalDevice' 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.
+     */
+    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/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..858a9bd
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 structure contains flags used for enabling various debugging aspects
+ * in a HAL module. By default, all debugging aspects are turned off. They
+ * can be enabled during xTS tests for functionality that, for example, would
+ * otherwise require human intervention (e.g. connection of external devices).
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable ModuleDebug {
+    /**
+     * When set to 'true', HAL module must simulate connection of external
+     * devices. An external device becomes 'connected' after a call to
+     * IModule.connectExternalDevice, simulation of connection requires:
+     *  - provision of at least one non-dynamic device port profile on
+     *    connection (as if it was retrieved from a connected device);
+     *  - simulating successful application of port configurations for reported
+     *    profiles.
+     */
+    boolean simulateDeviceConnections;
+}
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
new file mode 100644
index 0000000..ad1d9c7
--- /dev/null
+++ b/audio/aidl/default/Android.bp
@@ -0,0 +1,49 @@
+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",
+        "libstagefright_foundation",
+        "android.media.audio.common.types-V1-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",
+        "libstagefright_foundation",
+        "android.media.audio.common.types-V1-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..f5d679b
--- /dev/null
+++ b/audio/aidl/default/Configuration.cpp
@@ -0,0 +1,290 @@
+/*
+ * 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/AudioFormatDescription.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 <media/stagefright/foundation/MediaDefs.h>
+
+#include "core-impl/Configuration.h"
+
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+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 void fillProfile(AudioProfile* profile, const std::vector<int32_t>& channelLayouts,
+                        const std::vector<int32_t>& sampleRates) {
+    for (auto layout : channelLayouts) {
+        profile->channelMasks.push_back(
+                AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout));
+    }
+    profile->sampleRates.insert(profile->sampleRates.end(), sampleRates.begin(), sampleRates.end());
+}
+
+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;
+    fillProfile(&profile, channelLayouts, sampleRates);
+    return profile;
+}
+
+static AudioProfile createProfile(const std::string& encodingType,
+                                  const std::vector<int32_t>& channelLayouts,
+                                  const std::vector<int32_t>& sampleRates) {
+    AudioProfile profile;
+    profile.format.encoding = encodingType;
+    fillProfile(&profile, channelLayouts, sampleRates);
+    return profile;
+}
+
+static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags,
+                                    std::string connection = "") {
+    AudioPortDeviceExt deviceExt;
+    deviceExt.device.type.type = devType;
+    deviceExt.device.type.connection = std::move(connection);
+    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:
+//
+// Device ports:
+//  * "Null", OUT_SPEAKER, default
+//    - no profiles specified
+//  * "Loopback Out", OUT_SUBMIX
+//    - profile PCM 24-bit; STEREO; 48000
+//  * "USB Out", OUT_DEVICE, CONNECTION_USB
+//    - no profiles specified
+//  * "Zero", IN_MICROPHONE, default
+//    - no profiles specified
+//  * "Loopback In", IN_SUBMIX
+//    - profile PCM 24-bit; STEREO; 48000
+//  * "USB In", IN_DEVICE, CONNECTION_USB
+//    - no profiles specified
+//
+// Mix ports:
+//  * "primary output", PRIMARY, 1 max open, 1 max active stream
+//    - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+//    - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+//  * "compressed offload", DIRECT|COMPRESS_OFFLOAD|NON_BLOCKING, 1 max open, 1 max active stream
+//    - profile MP3; MONO, STEREO; 44100, 48000
+//  * "loopback output", stream count unlimited
+//    - profile PCM 24-bit; STEREO; 48000
+//  * "primary input", 2 max open, 2 max active streams
+//    - profile PCM 16-bit; MONO, STEREO, FRONT_BACK;
+//        8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+//    - profile PCM 24-bit; MONO, STEREO, FRONT_BACK;
+//        8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+//  * "loopback input", stream count unlimited
+//    - profile PCM 24-bit; STEREO; 48000
+//
+// Routes:
+//  "primary out", "compressed offload" -> "Null"
+//  "primary out", "compressed offload" -> "USB Out"
+//  "loopback out" -> "Loopback Out"
+//  "Zero", "USB In" -> "primary input"
+//  "Loopback In" -> "loopback input"
+//
+// Initial port configs:
+//  * "Null" device port: PCM 24-bit; STEREO; 48000
+//  * "Zero" device port: PCM 24-bit; MONO; 48000
+//
+// Profiles for device port connected state:
+//  * USB Out":
+//    - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+//    - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+//  * USB In":
+//    - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+//    - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+//
+Configuration& getNullPrimaryConfiguration() {
+    static Configuration configuration = []() {
+        const std::vector<AudioProfile> standardPcmAudioProfiles = {
+                createProfile(PcmType::INT_16_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+                              {44100, 48000}),
+                createProfile(PcmType::INT_24_BIT,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+                              {44100, 48000})};
+        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);
+        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 primaryOutMix = createPort(c.nextPortId++, "primary output",
+                                             1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
+                                             false, createPortMixExt(1, 1));
+        primaryOutMix.profiles.insert(primaryOutMix.profiles.begin(),
+                                      standardPcmAudioProfiles.begin(),
+                                      standardPcmAudioProfiles.end());
+        c.ports.push_back(primaryOutMix);
+
+        AudioPort compressedOffloadOutMix =
+                createPort(c.nextPortId++, "compressed offload",
+                           1 << static_cast<int32_t>(AudioOutputFlags::DIRECT) |
+                                   1 << static_cast<int32_t>(AudioOutputFlags::COMPRESS_OFFLOAD) |
+                                   1 << static_cast<int32_t>(AudioOutputFlags::NON_BLOCKING),
+                           false, createPortMixExt(1, 1));
+        compressedOffloadOutMix.profiles.push_back(
+                createProfile(::android::MEDIA_MIMETYPE_AUDIO_MPEG,
+                              {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+                              {44100, 48000}));
+        c.ports.push_back(compressedOffloadOutMix);
+
+        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);
+
+        AudioPort usbOutDevice =
+                createPort(c.nextPortId++, "USB Out", 0, false,
+                           createDeviceExt(AudioDeviceType::OUT_DEVICE, 0,
+                                           AudioDeviceDescription::CONNECTION_USB));
+        c.ports.push_back(usbOutDevice);
+        c.connectedProfiles[usbOutDevice.id] = standardPcmAudioProfiles;
+
+        AudioPort zeroInDevice =
+                createPort(c.nextPortId++, "Zero", 0, true,
+                           createDeviceExt(AudioDeviceType::IN_MICROPHONE,
+                                           1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+        c.ports.push_back(zeroInDevice);
+        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 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);
+
+        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);
+
+        AudioPort usbInDevice = createPort(c.nextPortId++, "USB In", 0, true,
+                                           createDeviceExt(AudioDeviceType::IN_DEVICE, 0,
+                                                           AudioDeviceDescription::CONNECTION_USB));
+        c.ports.push_back(usbInDevice);
+        c.connectedProfiles[usbInDevice.id] = standardPcmAudioProfiles;
+
+        c.routes.push_back(
+                createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, nullOutDevice.id));
+        c.routes.push_back(
+                createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, usbOutDevice.id));
+        c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+        c.routes.push_back(createRoute({zeroInDevice.id, usbInDevice.id}, primaryInMix.id));
+        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..5b4d48a
--- /dev/null
+++ b/audio/aidl/default/Module.cpp
@@ -0,0 +1,696 @@
+/*
+ * 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"
+#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::setModuleDebug(
+        const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) {
+    LOG(DEBUG) << __func__ << ": old flags:" << mDebug.toString()
+               << ", new flags: " << in_debug.toString();
+    if (mDebug.simulateDeviceConnections != in_debug.simulateDeviceConnections &&
+        !mConnectedDevicePorts.empty()) {
+        LOG(ERROR) << __func__ << ": attempting to change device connections simulation "
+                   << "while having external devices connected";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mDebug = in_debug;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData,
+                                                 AudioPort* _aidl_return) {
+    const int32_t templateId = in_templateIdAndAdditionalData.id;
+    auto& ports = getConfig().ports;
+    AudioPort connectedPort;
+    {  // Scope the template port so that we don't accidentally modify it.
+        auto templateIt = findById<AudioPort>(ports, templateId);
+        if (templateIt == ports.end()) {
+            LOG(ERROR) << __func__ << ": port id " << templateId << " not found";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        if (templateIt->ext.getTag() != AudioPortExt::Tag::device) {
+            LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        if (!templateIt->profiles.empty()) {
+            LOG(ERROR) << __func__ << ": port id " << templateId
+                       << " does not have dynamic profiles";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
+        if (templateDevicePort.device.type.connection.empty()) {
+            LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        // Postpone id allocation until we ensure that there are no client errors.
+        connectedPort = *templateIt;
+        connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
+        const auto& inputDevicePort =
+                in_templateIdAndAdditionalData.ext.get<AudioPortExt::Tag::device>();
+        auto& connectedDevicePort = connectedPort.ext.get<AudioPortExt::Tag::device>();
+        connectedDevicePort.device.address = inputDevicePort.device.address;
+        LOG(DEBUG) << __func__ << ": device port " << connectedPort.id << " device set to "
+                   << connectedDevicePort.device.toString();
+        // Check if there is already a connected port with for the same external device.
+        for (auto connectedPortId : mConnectedDevicePorts) {
+            auto connectedPortIt = findById<AudioPort>(ports, connectedPortId);
+            if (connectedPortIt->ext.get<AudioPortExt::Tag::device>().device ==
+                connectedDevicePort.device) {
+                LOG(ERROR) << __func__ << ": device " << connectedDevicePort.device.toString()
+                           << " is already connected at the device port id " << connectedPortId;
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+            }
+        }
+    }
+
+    if (!mDebug.simulateDeviceConnections) {
+        // In a real HAL here we would attempt querying the profiles from the device.
+        LOG(ERROR) << __func__ << ": failed to query supported device profiles";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+
+    connectedPort.id = ++getConfig().nextPortId;
+    mConnectedDevicePorts.insert(connectedPort.id);
+    LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
+               << "connected port ID " << connectedPort.id;
+    auto& connectedProfiles = getConfig().connectedProfiles;
+    if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+        connectedProfilesIt != connectedProfiles.end()) {
+        connectedPort.profiles = connectedProfilesIt->second;
+    }
+    ports.push_back(connectedPort);
+    *_aidl_return = std::move(connectedPort);
+
+    std::vector<AudioRoute> newRoutes;
+    auto& routes = getConfig().routes;
+    for (auto& r : routes) {
+        if (r.sinkPortId == templateId) {
+            AudioRoute newRoute;
+            newRoute.sourcePortIds = r.sourcePortIds;
+            newRoute.sinkPortId = connectedPort.id;
+            newRoute.isExclusive = r.isExclusive;
+            newRoutes.push_back(std::move(newRoute));
+        } else {
+            auto& srcs = r.sourcePortIds;
+            if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
+                srcs.push_back(connectedPort.id);
+            }
+        }
+    }
+    routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
+    auto& ports = getConfig().ports;
+    auto portIt = findById<AudioPort>(ports, in_portId);
+    if (portIt == ports.end()) {
+        LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (portIt->ext.getTag() != AudioPortExt::Tag::device) {
+        LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a device port";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    if (mConnectedDevicePorts.count(in_portId) == 0) {
+        LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    auto& configs = getConfig().portConfigs;
+    auto& initials = getConfig().initialConfigs;
+    auto configIt = std::find_if(configs.begin(), configs.end(), [&](const auto& config) {
+        if (config.portId == in_portId) {
+            // Check if the configuration was provided by the client.
+            const auto& initialIt = findById<AudioPortConfig>(initials, config.id);
+            return initialIt == initials.end() || config != *initialIt;
+        }
+        return false;
+    });
+    if (configIt != configs.end()) {
+        LOG(ERROR) << __func__ << ": port id " << in_portId << " has a non-default config with id "
+                   << configIt->id;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    ports.erase(portIt);
+    mConnectedDevicePorts.erase(in_portId);
+    LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
+
+    auto& routes = getConfig().routes;
+    for (auto routesIt = routes.begin(); routesIt != routes.end();) {
+        if (routesIt->sinkPortId == in_portId) {
+            routesIt = routes.erase(routesIt);
+        } else {
+            // Note: the list of sourcePortIds can't become empty because there must
+            // be the id of the template port in the route.
+            erase_if(routesIt->sourcePortIds, [in_portId](auto src) { return src == in_portId; });
+            ++routesIt;
+        }
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+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::getAudioRoutesForAudioPort(int32_t in_portId,
+                                                      std::vector<AudioRoute>* _aidl_return) {
+    auto& ports = getConfig().ports;
+    if (auto portIt = findById<AudioPort>(ports, in_portId); portIt == ports.end()) {
+        LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    auto& routes = getConfig().routes;
+    std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
+                 [&](const auto& r) {
+                     const auto& srcs = r.sourcePortIds;
+                     return r.sinkPortId == in_portId ||
+                            std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
+                 });
+    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 (portConfigIt->flags.has_value() &&
+        ((portConfigIt->flags.value().get<AudioIoFlags::Tag::output>() &
+          1 << static_cast<int32_t>(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0) &&
+        !in_offloadInfo.has_value()) {
+        LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+                   << " has COMPRESS_OFFLOAD flag set, requires offload info";
+        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) {
+    LOG(DEBUG) << __func__ << ": requested patch " << in_requested.toString();
+    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..d5cd30b
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <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;
+    // Port id -> List of profiles to use when the device port state is set to 'connected'.
+    std::map<int32_t, std::vector<::aidl::android::media::audio::common::AudioProfile>>
+            connectedProfiles;
+    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..81a02ba
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -0,0 +1,85 @@
+/*
+ * 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 <set>
+
+#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 setModuleDebug(
+            const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
+    ndk::ScopedAStatus connectExternalDevice(
+            const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData,
+            ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
+    ndk::ScopedAStatus disconnectExternalDevice(int32_t in_portId) override;
+    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 getAudioRoutesForAudioPort(
+            int32_t in_portId,
+            std::vector<::aidl::android::hardware::audio::core::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;
+    ModuleDebug mDebug;
+    // ids of ports created at runtime via 'connectExternalDevice'.
+    std::set<int32_t> mConnectedDevicePorts;
+    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..9d06f08
--- /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 container 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(); it != c.end();) {
+        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..aeb9983
--- /dev/null
+++ b/audio/aidl/default/main.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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() {
+    // This is a debug implementation, always enable debug logging.
+    android::base::SetMinimumLogSeverity(::android::base::DEBUG);
+    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_EQ(STATUS_OK, status);
+
+    // 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_EQ(STATUS_OK, status);
+
+    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..cd5915b
--- /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_ndk",
+    ],
+    static_libs: [
+        "android.hardware.audio.common-V1-ndk",
+        "android.hardware.audio.core-V1-ndk",
+        "android.media.audio.common.types-V1-ndk",
+    ],
+    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..969b0e9
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -0,0 +1,362 @@
+/*
+ * 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 <chrono>
+
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+using namespace std::chrono_literals;
+
+using aidl::android::hardware::audio::core::IModule;
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioEncapsulationMode;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+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::AudioUsage;
+using aidl::android::media::audio::common::Int;
+
+// static
+std::optional<AudioOffloadInfo> ModuleConfig::generateOffloadInfoIfNeeded(
+        const AudioPortConfig& portConfig) {
+    if (portConfig.flags.has_value() &&
+        portConfig.flags.value().getTag() == AudioIoFlags::Tag::output &&
+        (portConfig.flags.value().get<AudioIoFlags::Tag::output>() &
+         1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0) {
+        AudioOffloadInfo offloadInfo;
+        offloadInfo.base.sampleRate = portConfig.sampleRate.value().value;
+        offloadInfo.base.channelMask = portConfig.channelMask.value();
+        offloadInfo.base.format = portConfig.format.value();
+        offloadInfo.bitRatePerSecond = 256;                                // Arbitrary value.
+        offloadInfo.durationUs = std::chrono::microseconds(1min).count();  // Arbitrary value.
+        offloadInfo.usage = AudioUsage::MEDIA;
+        offloadInfo.encapsulationMode = AudioEncapsulationMode::NONE;
+        return offloadInfo;
+    }
+    return {};
+}
+
+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>();
+        if (devicePort.device.type.connection.empty()) {
+            const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
+            // Permanently attached device.
+            if (isInput) {
+                mAttachedSourceDevicePorts.insert(port.id);
+            } else {
+                mAttachedSinkDevicePorts.insert(port.id);
+            }
+        } else if (port.profiles.empty()) {
+            mExternalDevicePorts.insert(port.id);
+        }
+    }
+    if (!mStatus.isOk()) return;
+    mStatus = module->getAudioRoutes(&mRoutes);
+    if (!mStatus.isOk()) return;
+    mStatus = module->getAudioPortConfigs(&mInitialConfigs);
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedDevicePorts() const {
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+        return mAttachedSinkDevicePorts.count(port.id) != 0 ||
+               mAttachedSourceDevicePorts.count(port.id) != 0;
+    });
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getExternalDevicePorts() const {
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result),
+                 [&](const auto& port) { return mExternalDevicePorts.count(port.id) != 0; });
+    return result;
+}
+
+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;
+}
+
+std::string ModuleConfig::toString() const {
+    std::string result;
+    result.append("Ports: ");
+    result.append(android::internal::ToString(mPorts));
+    result.append("\nInitial configs: ");
+    result.append(android::internal::ToString(mInitialConfigs));
+    result.append("\nAttached sink device ports: ");
+    result.append(android::internal::ToString(mAttachedSinkDevicePorts));
+    result.append("\nAttached source device ports: ");
+    result.append(android::internal::ToString(mAttachedSourceDevicePorts));
+    result.append("\nExternal device ports: ");
+    result.append(android::internal::ToString(mExternalDevicePorts));
+    result.append("\nRoutes: ");
+    result.append(android::internal::ToString(mRoutes));
+    return result;
+}
+
+static size_t combineAudioConfigs(const AudioPort& port, const AudioProfile& profile,
+                                  std::vector<AudioPortConfig>* result) {
+    const size_t newConfigCount = profile.channelMasks.size() * profile.sampleRates.size();
+    result->reserve(result->capacity() + newConfigCount);
+    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.flags = port.flags;
+            config.ext = port.ext;
+            result->push_back(std::move(config));
+        }
+    }
+    return newConfigCount;
+}
+
+static bool isDynamicProfile(const AudioProfile& profile) {
+    return (profile.format.type == AudioFormatType::DEFAULT && profile.format.encoding.empty()) ||
+           profile.sampleRates.empty() || profile.channelMasks.empty();
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
+        const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
+    std::vector<AudioPortConfig> result;
+    for (const auto& mixPort : ports) {
+        if (getAttachedDevicesPortsForMixPort(isInput, mixPort).empty()) {
+            continue;
+        }
+        for (const auto& profile : mixPort.profiles) {
+            if (isDynamicProfile(profile)) continue;
+            combineAudioConfigs(mixPort, profile, &result);
+            if (singleProfile && !result.empty()) {
+                result.resize(1);
+                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) {
+            combineAudioConfigs(devicePort, profile, &result);
+            if (singleProfile && !result.empty()) {
+                result.resize(1);
+                return result;
+            }
+        }
+        if (resultSizeBefore == result.size()) {
+            std::copy_if(mInitialConfigs.begin(), mInitialConfigs.end(), std::back_inserter(result),
+                         [&](const auto& config) { return config.portId == devicePort.id; });
+            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..df13430
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -0,0 +1,139 @@
+/*
+ * 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 <aidl/android/hardware/audio/core/AudioRoute.h>
+#include <aidl/android/hardware/audio/core/IModule.h>
+#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
+#include <aidl/android/media/audio/common/AudioPort.h>
+
+class ModuleConfig {
+  public:
+    using SrcSinkPair = std::pair<aidl::android::media::audio::common::AudioPortConfig,
+                                  aidl::android::media::audio::common::AudioPortConfig>;
+    using SrcSinkGroup =
+            std::pair<aidl::android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
+
+    static std::optional<aidl::android::media::audio::common::AudioOffloadInfo>
+    generateOffloadInfoIfNeeded(
+            const aidl::android::media::audio::common::AudioPortConfig& portConfig);
+
+    explicit ModuleConfig(aidl::android::hardware::audio::core::IModule* module);
+    const ndk::ScopedAStatus& getStatus() const { return mStatus; }
+    std::string getError() const { return mStatus.getMessage(); }
+
+    std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getExternalDevicePorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getInputMixPorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getOutputMixPorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
+        return isInput ? getInputMixPorts() : getOutputMixPorts();
+    }
+
+    std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+            bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
+        return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort)
+                       : getAttachedSinkDevicesPortsForMixPort(mixPort);
+    }
+    std::vector<aidl::android::media::audio::common::AudioPort>
+    getAttachedSinkDevicesPortsForMixPort(
+            const aidl::android::media::audio::common::AudioPort& mixPort) const;
+    std::vector<aidl::android::media::audio::common::AudioPort>
+    getAttachedSourceDevicesPortsForMixPort(
+            const aidl::android::media::audio::common::AudioPort& mixPort) const;
+    std::optional<aidl::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<aidl::android::media::audio::common::AudioPortConfig>
+    getPortConfigsForAttachedDevicePorts() const {
+        return generateAudioDevicePortConfigs(getAttachedDevicePorts(), false);
+    }
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts()
+            const {
+        auto inputs = generateAudioMixPortConfigs(getInputMixPorts(), true, false);
+        auto outputs = generateAudioMixPortConfigs(getOutputMixPorts(), false, false);
+        inputs.insert(inputs.end(), outputs.begin(), outputs.end());
+        return inputs;
+    }
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+            bool isInput) const {
+        return generateAudioMixPortConfigs(getMixPorts(isInput), isInput, false);
+    }
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+            bool isInput, const aidl::android::media::audio::common::AudioPort& port) const {
+        return generateAudioMixPortConfigs({port}, isInput, false);
+    }
+    std::optional<aidl::android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+            bool isInput) const {
+        const auto config = generateAudioMixPortConfigs(getMixPorts(isInput), isInput, true);
+        if (!config.empty()) {
+            return *config.begin();
+        }
+        return {};
+    }
+    std::optional<aidl::android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+            bool isInput, const aidl::android::media::audio::common::AudioPort& port) const {
+        const auto config = generateAudioMixPortConfigs({port}, isInput, true);
+        if (!config.empty()) {
+            return *config.begin();
+        }
+        return {};
+    }
+
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForDevicePort(
+            const aidl::android::media::audio::common::AudioPort& port) const {
+        return generateAudioDevicePortConfigs({port}, false);
+    }
+    aidl::android::media::audio::common::AudioPortConfig getSingleConfigForDevicePort(
+            const aidl::android::media::audio::common::AudioPort& port) const {
+        const auto config = generateAudioDevicePortConfigs({port}, true);
+        return *config.begin();
+    }
+
+    std::string toString() const;
+
+  private:
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
+            const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
+            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, its initial configs are looked up, if there are none,
+    // then an empty config is used, assuming further negotiation via setAudioPortConfig.
+    std::vector<aidl::android::media::audio::common::AudioPortConfig>
+    generateAudioDevicePortConfigs(
+            const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
+            bool singleProfile) const;
+
+    ndk::ScopedAStatus mStatus = ndk::ScopedAStatus::ok();
+    std::vector<aidl::android::media::audio::common::AudioPort> mPorts;
+    std::vector<aidl::android::media::audio::common::AudioPortConfig> mInitialConfigs;
+    std::set<int32_t> mAttachedSinkDevicePorts;
+    std::set<int32_t> mAttachedSourceDevicePorts;
+    std::set<int32_t> mExternalDevicePorts;
+    std::vector<aidl::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..bb24365
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -0,0 +1,1503 @@
+/*
+ * 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>
+
+#define LOG_TAG "VtsHalAudioCore"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/audio/core/IConfig.h>
+#include <aidl/android/hardware/audio/core/IModule.h>
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+#include <android-base/properties.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+using aidl::android::hardware::audio::common::PlaybackTrackMetadata;
+using aidl::android::hardware::audio::common::RecordTrackMetadata;
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::audio::core::AudioPatch;
+using aidl::android::hardware::audio::core::AudioRoute;
+using aidl::android::hardware::audio::core::IModule;
+using aidl::android::hardware::audio::core::IStreamIn;
+using aidl::android::hardware::audio::core::IStreamOut;
+using aidl::android::hardware::audio::core::ModuleDebug;
+using aidl::android::media::audio::common::AudioContentType;
+using aidl::android::media::audio::common::AudioDevice;
+using aidl::android::media::audio::common::AudioDeviceAddress;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioFormatType;
+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::AudioSource;
+using aidl::android::media::audio::common::AudioUsage;
+using ndk::ScopedAStatus;
+
+namespace ndk {
+std::ostream& operator<<(std::ostream& str, const ScopedAStatus& status) {
+    str << status.getDescription();
+    return str;
+}
+}  // namespace ndk
+
+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;
+}
+
+AudioDeviceAddress GenerateUniqueDeviceAddress() {
+    static int nextId = 1;
+    // TODO: Use connection-specific ID.
+    return AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(std::to_string(++nextId));
+}
+
+struct AidlDeathRecipient {
+    const ndk::SpAIBinder binder;
+    const ndk::ScopedAIBinder_DeathRecipient recipient;
+    std::mutex mutex;
+    std::condition_variable condition;
+    bool fired = false;
+
+    explicit AidlDeathRecipient(const ndk::SpAIBinder& binder)
+        : binder(binder), recipient(AIBinder_DeathRecipient_new(&binderDiedCallbackAidl)) {}
+
+    binder_status_t linkToDeath() {
+        return AIBinder_linkToDeath(binder.get(), recipient.get(), this);
+    }
+
+    void binderDied() {
+        std::unique_lock<std::mutex> lock(mutex);
+        fired = true;
+        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;
+    }
+
+    static void binderDiedCallbackAidl(void* cookie) {
+        AidlDeathRecipient* self = static_cast<AidlDeathRecipient*>(cookie);
+        self->binderDied();
+    }
+};
+
+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;
+}
+
+// All 'With*' classes are move-only because they are associated with some
+// resource or state of a HAL module.
+class WithDebugFlags {
+  public:
+    static WithDebugFlags createNested(const WithDebugFlags& parent) {
+        return WithDebugFlags(parent.mFlags);
+    }
+
+    WithDebugFlags() {}
+    explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
+    WithDebugFlags(const WithDebugFlags&) = delete;
+    WithDebugFlags& operator=(const WithDebugFlags&) = delete;
+    ~WithDebugFlags() {
+        if (mModule != nullptr) {
+            ScopedAStatus status = mModule->setModuleDebug(mInitial);
+            EXPECT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        }
+    }
+    void SetUp(IModule* module) {
+        ScopedAStatus status = module->setModuleDebug(mFlags);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    ModuleDebug& flags() { return mFlags; }
+
+  private:
+    ModuleDebug mInitial;
+    ModuleDebug mFlags;
+    IModule* mModule = nullptr;
+};
+
+// 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(const WithAudioPortConfig&) = delete;
+    WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete;
+    ~WithAudioPortConfig() {
+        if (mModule != nullptr) {
+            ScopedAStatus status = mModule->resetAudioPortConfig(getId());
+            EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                    << 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;
+            ScopedAStatus status = module->setAudioPortConfig(mInitialConfig, &suggested, &applied);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                    << 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;
+};
+
+class AudioCoreModule : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(ConnectToService());
+        debug.flags().simulateDeviceConnections = true;
+        ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
+    }
+
+    void TearDown() override {
+        if (module != nullptr) {
+            ScopedAStatus status = module->setModuleDebug(ModuleDebug{});
+            EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                    << status << " returned when resetting debug flags";
+        }
+    }
+
+    void ConnectToService() {
+        module = IModule::fromBinder(
+                ndk::SpAIBinder(AServiceManager_getService(GetParam().c_str())));
+        ASSERT_NE(module, nullptr);
+    }
+
+    void RestartService() {
+        ASSERT_NE(module, nullptr);
+        moduleConfig.reset();
+        deathHandler.reset(new AidlDeathRecipient(module->asBinder()));
+        ASSERT_EQ(STATUS_OK, deathHandler->linkToDeath());
+        ASSERT_TRUE(base::SetProperty("sys.audio.restart.hal", "1"));
+        EXPECT_TRUE(deathHandler->waitForFired(3000));
+        deathHandler.reset();
+        ASSERT_NO_FATAL_FAILURE(ConnectToService());
+    }
+
+    void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
+        for (const auto& config : configs) {
+            ASSERT_NE(0, config.portId);
+            WithAudioPortConfig portConfig(config);
+            ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));  // calls setAudioPortConfig
+            EXPECT_EQ(config.portId, portConfig.get().portId);
+            std::vector<AudioPortConfig> retrievedPortConfigs;
+            ScopedAStatus status = module->getAudioPortConfigs(&retrievedPortConfigs);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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 getAudioPortConfigs";
+            if (configIt != retrievedPortConfigs.end()) {
+                EXPECT_EQ(portConfig.get(), *configIt)
+                        << "Applied port config returned by setAudioPortConfig: "
+                        << portConfig.get().toString()
+                        << " is not the same as retrieved via getAudioPortConfigs: "
+                        << configIt->toString();
+            }
+        }
+    }
+
+    template <typename Entity>
+    void GetAllEntityIds(std::set<int32_t>* entityIds,
+                         ScopedAStatus (IModule::*getter)(std::vector<Entity>*),
+                         const std::string& errorMessage) {
+        std::vector<Entity> entities;
+        {
+            ScopedAStatus status = (module.get()->*getter)(&entities);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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(EX_NONE, moduleConfig->getStatus().getExceptionCode())
+                    << "ModuleConfig init error: " << moduleConfig->getError();
+        }
+    }
+
+    std::shared_ptr<IModule> module;
+    std::unique_ptr<AidlDeathRecipient> deathHandler;
+    std::unique_ptr<ModuleConfig> moduleConfig;
+    WithDebugFlags debug;
+};
+
+class WithDevicePortConnectedState {
+  public:
+    explicit WithDevicePortConnectedState(const AudioPort& idAndData) : mIdAndData(idAndData) {}
+    WithDevicePortConnectedState(const AudioPort& id, const AudioDeviceAddress& address)
+        : mIdAndData(setAudioPortAddress(id, address)) {}
+    WithDevicePortConnectedState(const WithDevicePortConnectedState&) = delete;
+    WithDevicePortConnectedState& operator=(const WithDevicePortConnectedState&) = delete;
+    ~WithDevicePortConnectedState() {
+        if (mModule != nullptr) {
+            ScopedAStatus status = mModule->disconnectExternalDevice(getId());
+            EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                    << status << " returned when disconnecting device port ID " << getId();
+        }
+    }
+    void SetUp(IModule* module) {
+        ScopedAStatus status = module->connectExternalDevice(mIdAndData, &mConnectedPort);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                << status << " returned when connecting device port ID & data "
+                << mIdAndData.toString();
+        ASSERT_NE(mIdAndData.id, getId())
+                << "ID of the connected port must not be the same as the ID of the template port";
+        mModule = module;
+    }
+    int32_t getId() const { return mConnectedPort.id; }
+    const AudioPort& get() { return mConnectedPort; }
+
+  private:
+    static AudioPort setAudioPortAddress(const AudioPort& id, const AudioDeviceAddress& address) {
+        AudioPort result = id;
+        result.ext.get<AudioPortExt::Tag::device>().device.address = address;
+        return result;
+    }
+
+    const AudioPort mIdAndData;
+    IModule* mModule = nullptr;
+    AudioPort mConnectedPort;
+};
+
+template <typename Stream>
+class WithStream {
+  public:
+    WithStream() {}
+    explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
+    WithStream(const WithStream&) = delete;
+    WithStream& operator=(const WithStream&) = delete;
+    ~WithStream() {
+        if (mStream != nullptr) {
+            ScopedAStatus status = mStream->close();
+            EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                    << status << "; port config id " << getPortId();
+        }
+    }
+    void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); }
+    ScopedAStatus SetUpNoChecks(IModule* module) {
+        return SetUpNoChecks(module, mPortConfig.get());
+    }
+    ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig);
+    void SetUp(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+        ScopedAStatus status = SetUpNoChecks(module);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                << status << "; port config id " << getPortId();
+        ASSERT_NE(nullptr, mStream) << "; port config id " << getPortId();
+    }
+    Stream* get() const { return mStream.get(); }
+    std::shared_ptr<Stream> getSharedPointer() const { return mStream; }
+    const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
+    int32_t getPortId() const { return mPortConfig.getId(); }
+
+  private:
+    WithAudioPortConfig mPortConfig;
+    std::shared_ptr<Stream> mStream;
+};
+
+SinkMetadata GenerateSinkMetadata(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 metadata;
+}
+
+template <>
+ScopedAStatus WithStream<IStreamIn>::SetUpNoChecks(IModule* module,
+                                                   const AudioPortConfig& portConfig) {
+    return module->openInputStream(portConfig.id, GenerateSinkMetadata(portConfig), &mStream);
+}
+
+SourceMetadata GenerateSourceMetadata(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 metadata;
+}
+
+template <>
+ScopedAStatus WithStream<IStreamOut>::SetUpNoChecks(IModule* module,
+                                                    const AudioPortConfig& portConfig) {
+    return module->openOutputStream(portConfig.id, GenerateSourceMetadata(portConfig),
+                                    ModuleConfig::generateOffloadInfoIfNeeded(portConfig),
+                                    &mStream);
+}
+
+class WithAudioPatch {
+  public:
+    WithAudioPatch() {}
+    WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
+        : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
+    WithAudioPatch(const WithAudioPatch&) = delete;
+    WithAudioPatch& operator=(const WithAudioPatch&) = delete;
+    ~WithAudioPatch() {
+        if (mModule != nullptr && mPatch.id != 0) {
+            ScopedAStatus status = mModule->resetAudioPatch(mPatch.id);
+            EXPECT_EQ(EX_NONE, status.getExceptionCode()) << status << "; patch id " << getId();
+        }
+    }
+    void SetUpPortConfigs(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module));
+        ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module));
+    }
+    ScopedAStatus 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));
+        ScopedAStatus status = SetUpNoChecks(module);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                << 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, GetAudioPortsIsStable) {
+    std::vector<AudioPort> ports1;
+    {
+        ScopedAStatus status = module->getAudioPorts(&ports1);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    std::vector<AudioPort> ports2;
+    {
+        ScopedAStatus status = module->getAudioPorts(&ports2);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    ASSERT_EQ(ports1.size(), ports2.size())
+            << "Sizes of audio port arrays do not match across consequent calls to getAudioPorts";
+    std::sort(ports1.begin(), ports1.end());
+    std::sort(ports2.begin(), ports2.end());
+    EXPECT_EQ(ports1, ports2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesIsStable) {
+    std::vector<AudioRoute> routes1;
+    {
+        ScopedAStatus status = module->getAudioRoutes(&routes1);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    std::vector<AudioRoute> routes2;
+    {
+        ScopedAStatus status = module->getAudioRoutes(&routes2);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    ASSERT_EQ(routes1.size(), routes2.size())
+            << "Sizes of audio route arrays do not match across consequent 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;
+    {
+        ScopedAStatus status = module->getAudioRoutes(&routes);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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;
+    {
+        ScopedAStatus status = module->getAudioRoutes(&routes);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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, GetAudioRoutesForAudioPort) {
+    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) {
+        std::vector<AudioRoute> routes;
+        ScopedAStatus status = module->getAudioRoutesForAudioPort(portId, &routes);
+        EXPECT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        for (const auto& r : routes) {
+            if (r.sinkPortId != portId) {
+                const auto& srcs = r.sourcePortIds;
+                EXPECT_TRUE(std::find(srcs.begin(), srcs.end(), portId) != srcs.end())
+                        << " port ID " << portId << " does not used by the route " << r.toString();
+            }
+        }
+    }
+    for (const auto portId : GetNonExistentIds(portIds)) {
+        std::vector<AudioRoute> routes;
+        ScopedAStatus status = module->getAudioRoutesForAudioPort(portId, &routes);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned for port ID " << portId;
+    }
+}
+
+TEST_P(AudioCoreModule, CheckDevicePorts) {
+    std::vector<AudioPort> ports;
+    {
+        ScopedAStatus status = module->getAudioPorts(&ports);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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;
+    {
+        ScopedAStatus status = module->getAudioPorts(&ports);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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;
+        ScopedAStatus status = module->getAudioPort(portId, &port);
+        EXPECT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        EXPECT_EQ(portId, port.id);
+    }
+    for (const auto portId : GetNonExistentIds(portIds)) {
+        AudioPort port;
+        ScopedAStatus status = module->getAudioPort(portId, &port);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned for port ID " << portId;
+    }
+}
+
+TEST_P(AudioCoreModule, SetUpModuleConfig) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    // Send the module config to logcat to facilitate failures investigation.
+    LOG(INFO) << "SetUpModuleConfig: " << moduleConfig->toString();
+}
+
+// Verify that HAL module reports for a connected device port at least one non-dynamic profile,
+// that is, a profile with actual supported configuration.
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    for (const auto& port : ports) {
+        AudioPort portWithData = port;
+        portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+                GenerateUniqueDeviceAddress();
+        WithDevicePortConnectedState portConnected(portWithData);
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        const int32_t connectedPortId = portConnected.getId();
+        ASSERT_NE(portWithData.id, connectedPortId);
+        ASSERT_EQ(portWithData.ext.getTag(), portConnected.get().ext.getTag());
+        EXPECT_EQ(portWithData.ext.get<AudioPortExt::Tag::device>().device,
+                  portConnected.get().ext.get<AudioPortExt::Tag::device>().device);
+        // Verify that 'getAudioPort' and 'getAudioPorts' return the same connected port.
+        AudioPort connectedPort;
+        ScopedAStatus status = module->getAudioPort(connectedPortId, &connectedPort);
+        EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                << status << " returned for getAudioPort port ID " << connectedPortId;
+        EXPECT_EQ(portConnected.get(), connectedPort);
+        const auto& portProfiles = connectedPort.profiles;
+        EXPECT_NE(0, portProfiles.size())
+                << "Connected port has no profiles: " << connectedPort.toString();
+        const auto dynamicProfileIt =
+                std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) {
+                    return profile.format.type == AudioFormatType::DEFAULT;
+                });
+        EXPECT_EQ(portProfiles.end(), dynamicProfileIt) << "Connected port contains dynamic "
+                                                        << "profiles: " << connectedPort.toString();
+
+        std::vector<AudioPort> allPorts;
+        {
+            ScopedAStatus status = module->getAudioPorts(&allPorts);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        }
+        const auto allPortsIt = findById(allPorts, connectedPortId);
+        EXPECT_NE(allPorts.end(), allPortsIt);
+        if (allPortsIt != allPorts.end()) {
+            EXPECT_EQ(portConnected.get(), *allPortsIt);
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
+    std::set<int32_t> portConfigIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+    for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
+        {
+            std::shared_ptr<IStreamIn> stream;
+            ScopedAStatus status = module->openInputStream(portConfigId, {}, &stream);
+            EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                    << status << " openInputStream returned for port config ID " << portConfigId;
+            EXPECT_EQ(nullptr, stream);
+        }
+        {
+            std::shared_ptr<IStreamOut> stream;
+            ScopedAStatus status = module->openOutputStream(portConfigId, {}, {}, &stream);
+            EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                    << 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;
+    {
+        ScopedAStatus status = module->getAudioPortConfigs(&portConfigs);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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)) {
+        ScopedAStatus status = module->resetAudioPortConfig(portConfigId);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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;
+    {
+        ScopedAStatus status = module->getAudioPortConfigs(&portConfigsBefore);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    // TODO: Change port configs according to port profiles.
+    for (const auto& c : portConfigsBefore) {
+        ScopedAStatus status = module->resetAudioPortConfig(c.id);
+        EXPECT_EQ(EX_NONE, status.getExceptionCode())
+                << status << " returned for port config ID " << c.id;
+    }
+    std::vector<AudioPortConfig> portConfigsAfter;
+    {
+        ScopedAStatus status = module->getAudioPortConfigs(&portConfigsAfter);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << 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;
+        ScopedAStatus status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                << 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, SetAllAttachedDevicePortConfigs) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForAttachedDevicePorts()));
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, SetAllExternalDevicePortConfigs) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    for (const auto& port : ports) {
+        WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(
+                ApplyEveryConfig(moduleConfig->getPortConfigsForDevicePort(portConnected.get())));
+    }
+}
+
+TEST_P(AudioCoreModule, SetAllStaticAudioPortConfigs) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForMixPorts()));
+}
+
+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;
+        ScopedAStatus status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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;
+        ScopedAStatus status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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());
+    }
+}
+
+TEST_P(AudioCoreModule, TryConnectMissingDevice) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    AudioPort ignored;
+    WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug);
+    doNotSimulateConnections.flags().simulateDeviceConnections = false;
+    ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
+    for (const auto& port : ports) {
+        AudioPort portWithData = port;
+        portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+                GenerateUniqueDeviceAddress();
+        ScopedAStatus status = module->connectExternalDevice(portWithData, &ignored);
+        EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                << status << " returned for static port " << portWithData.toString();
+    }
+}
+
+TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
+    ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+    ModuleDebug midwayDebugChange = debug.flags();
+    midwayDebugChange.simulateDeviceConnections = false;
+    ScopedAStatus status = module->setModuleDebug(midwayDebugChange);
+    EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+            << status << " returned when trying to disable connections simulation "
+            << "while having a connected device";
+}
+
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceInvalidPorts) {
+    AudioPort ignored;
+    std::set<int32_t> portIds;
+    ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+    for (const auto portId : GetNonExistentIds(portIds)) {
+        AudioPort invalidPort;
+        invalidPort.id = portId;
+        ScopedAStatus status = module->connectExternalDevice(invalidPort, &ignored);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned for port ID " << portId << " when setting CONNECTED state";
+        status = module->disconnectExternalDevice(portId);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned for port ID " << portId
+                << " when setting DISCONNECTED state";
+    }
+
+    std::vector<AudioPort> ports;
+    {
+        ScopedAStatus status = module->getAudioPorts(&ports);
+        ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+    }
+    for (const auto& port : ports) {
+        if (port.ext.getTag() != AudioPortExt::Tag::device) {
+            ScopedAStatus status = module->connectExternalDevice(port, &ignored);
+            EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                    << status << " returned for non-device port ID " << port.id
+                    << " when setting CONNECTED state";
+            status = module->disconnectExternalDevice(port.id);
+            EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                    << status << " returned for non-device port ID " << port.id
+                    << " when setting DISCONNECTED state";
+        } else {
+            const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+            if (devicePort.device.type.connection.empty()) {
+                ScopedAStatus status = module->connectExternalDevice(port, &ignored);
+                EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                        << status << " returned for permanently attached device port ID " << port.id
+                        << " when setting CONNECTED state";
+                status = module->disconnectExternalDevice(port.id);
+                EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                        << status << " returned for permanently attached device port ID " << port.id
+                        << " when setting DISCONNECTED state";
+            }
+        }
+    }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceTwice) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    AudioPort ignored;
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    for (const auto& port : ports) {
+        ScopedAStatus status = module->disconnectExternalDevice(port.id);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned when disconnecting already disconnected device port ID "
+                << port.id;
+        AudioPort portWithData = port;
+        portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+                GenerateUniqueDeviceAddress();
+        WithDevicePortConnectedState portConnected(portWithData);
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        status = module->connectExternalDevice(portConnected.get(), &ignored);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned when trying to connect a connected device port "
+                << portConnected.get().toString();
+        status = module->connectExternalDevice(portWithData, &ignored);
+        EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                << status << " returned when connecting again the external device "
+                << portWithData.ext.get<AudioPortExt::Tag::device>().device.toString();
+        if (status.getExceptionCode() == EX_NONE) {
+            ADD_FAILURE() << "Returned connected port " << ignored.toString() << " for template "
+                          << portWithData.toString();
+        }
+    }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, DisconnectExternalDeviceNonResetPortConfig) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    for (const auto& port : ports) {
+        WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        const auto portConfig = moduleConfig->getSingleConfigForDevicePort(portConnected.get());
+        {
+            WithAudioPortConfig config(portConfig);
+            // Note: if SetUp fails, check the status of 'GetAudioPortWithExternalDevices' test.
+            // Our test assumes that 'getAudioPort' returns at least one profile, and it
+            // is not a dynamic profile.
+            ASSERT_NO_FATAL_FAILURE(config.SetUp(module.get()));
+            ScopedAStatus status = module->disconnectExternalDevice(portConnected.getId());
+            EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                    << status << " returned when trying to disconnect device port ID " << port.id
+                    << " with active configuration " << config.getId();
+        }
+    }
+}
+
+TEST_P(AudioCoreModule, ExternalDevicePortRoutes) {
+    ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+    std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+    if (ports.empty()) {
+        GTEST_SKIP() << "No external devices in the module.";
+    }
+    for (const auto& port : ports) {
+        std::vector<AudioRoute> routesBefore;
+        {
+            ScopedAStatus status = module->getAudioRoutes(&routesBefore);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        }
+
+        int32_t connectedPortId;
+        {
+            WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+            ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+            connectedPortId = portConnected.getId();
+            std::vector<AudioRoute> connectedPortRoutes;
+            {
+                ScopedAStatus status =
+                        module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes);
+                ASSERT_EQ(EX_NONE, status.getExceptionCode())
+                        << status << " returned when retrieving routes for connected port id "
+                        << connectedPortId;
+            }
+            // There must be routes for the port to be useful.
+            if (connectedPortRoutes.empty()) {
+                std::vector<AudioRoute> allRoutes;
+                ScopedAStatus status = module->getAudioRoutes(&allRoutes);
+                ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+                ADD_FAILURE() << " no routes returned for the connected port "
+                              << portConnected.get().toString()
+                              << "; all routes: " << android::internal::ToString(allRoutes);
+            }
+        }
+        std::vector<AudioRoute> ignored;
+        ScopedAStatus status = module->getAudioRoutesForAudioPort(connectedPortId, &ignored);
+        ASSERT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << status << " returned when retrieving routes for released connected port id "
+                << connectedPortId;
+
+        std::vector<AudioRoute> routesAfter;
+        {
+            ScopedAStatus status = module->getAudioRoutes(&routesAfter);
+            ASSERT_EQ(EX_NONE, status.getExceptionCode()) << status;
+        }
+        ASSERT_EQ(routesBefore.size(), routesAfter.size())
+                << "Sizes of audio route arrays do not match after creating and "
+                << "releasing a connected port";
+        std::sort(routesBefore.begin(), routesBefore.end());
+        std::sort(routesAfter.begin(), routesAfter.end());
+        EXPECT_EQ(routesBefore, routesAfter);
+    }
+}
+
+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";
+        }
+        std::shared_ptr<Stream> heldStream;
+        {
+            WithStream<Stream> stream(portConfig.value());
+            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+            heldStream = stream.getSharedPointer();
+        }
+        ScopedAStatus status = heldStream->close();
+        EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                << 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()));
+                    ScopedAStatus status = stream.SetUpNoChecks(module.get());
+                    EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                            << 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()));
+        ScopedAStatus status = stream.SetUpNoChecks(module.get());
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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()));
+        ScopedAStatus status = module->resetAudioPortConfig(stream.getPortId());
+        EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                << 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;
+        ScopedAStatus status = stream2.SetUpNoChecks(module.get(), stream1.getPortConfig());
+        EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                << 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()));
+}
+
+TEST_P(AudioStreamOut, RequireOffloadInfo) {
+    const auto mixPorts = moduleConfig->getMixPorts(false);
+    auto offloadPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [&](const AudioPort& port) {
+        constexpr int compressOffloadFlag = 1
+                                            << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD);
+        return port.flags.getTag() == AudioIoFlags::Tag::output &&
+               (port.flags.get<AudioIoFlags::Tag::output>() & compressOffloadFlag) != 0 &&
+               !moduleConfig->getAttachedSinkDevicesPortsForMixPort(port).empty();
+    });
+    if (offloadPortIt == mixPorts.end()) {
+        GTEST_SKIP()
+                << "No mix port for compressed offload that could be routed to attached devices";
+    }
+    const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *offloadPortIt);
+    ASSERT_TRUE(portConfig.has_value())
+            << "No profiles specified for the compressed offload mix port";
+    std::shared_ptr<IStreamOut> ignored;
+    ScopedAStatus status = module->openOutputStream(portConfig.value().id,
+                                                    GenerateSourceMetadata(portConfig.value()),
+                                                    {} /* offloadInfo */, &ignored);
+    EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+            << status
+            << " returned when no offload info is provided for a compressed offload mix port";
+}
+
+// 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;
+        ScopedAStatus status = module->setAudioPatch(patch, &patch);
+        ASSERT_EQ(expectedException, status.getExceptionCode())
+                << 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) {
+            ScopedAStatus status = module->resetAudioPortConfig(portConfigId);
+            EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
+                    << 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(EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()}));
+        EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
+                EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()},
+                {sinkPortConfig.getId()}));
+        EXPECT_NO_FATAL_FAILURE(
+                SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {}));
+        EXPECT_NO_FATAL_FAILURE(
+                SetInvalidPatchHelper(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(EX_ILLEGAL_ARGUMENT, {portConfigId},
+                                                          {sinkPortConfig.getId()}));
+            EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(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()));
+        ScopedAStatus status = patch.SetUpNoChecks(module.get());
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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<std::unique_ptr<WithAudioPatch>> patches;
+            for (const auto& srcSink : srcSinkGroup.second) {
+                if (!route.isExclusive) {
+                    patches.push_back(
+                            std::make_unique<WithAudioPatch>(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;
+            ScopedAStatus status =
+                    module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId);
+            EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                    << 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)) {
+        ScopedAStatus status = module->resetAudioPatch(patchId);
+        EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
+                << 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);
+
+class TestExecutionTracer : public ::testing::EmptyTestEventListener {
+  public:
+    void OnTestStart(const ::testing::TestInfo& test_info) override {
+        TraceTestState("Started", test_info);
+    }
+
+    void OnTestEnd(const ::testing::TestInfo& test_info) override {
+        TraceTestState("Completed", test_info);
+    }
+
+  private:
+    static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) {
+        LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
+    }
+};
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
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/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp b/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp
index 58423c8..7f90501 100644
--- a/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp
+++ b/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp
@@ -32,9 +32,10 @@
 
     // API have a requirement that width must be divied by 16 except yuyvtorgb
     int min_height = 2;
-    int max_height = (image_pixel_size / 16) & ~(1);  // must be even number
+    int max_height = (image_pixel_size / 16);
     int height = fdp.ConsumeIntegralInRange<uint32_t>(min_height, max_height);
-    int width = (image_pixel_size / height) & ~(16);  // must be divisible by 16
+    height &= ~(1);  // must be even number
+    int width = (image_pixel_size / height) & ~(0xF);  // must be divisible by 16
 
     uint8_t* src = (uint8_t*)(data + 4);
     uint32_t* tgt = (uint32_t*)malloc(sizeof(uint32_t) * image_pixel_size);
diff --git a/automotive/occupant_awareness/aidl/default/Android.bp b/automotive/occupant_awareness/aidl/default/Android.bp
index 66af9de..3dc7e0d 100644
--- a/automotive/occupant_awareness/aidl/default/Android.bp
+++ b/automotive/occupant_awareness/aidl/default/Android.bp
@@ -39,3 +39,28 @@
         "android.hardware.automotive.occupant_awareness-V1-ndk",
     ],
 }
+
+cc_fuzz {
+    name: "android.hardware.automotive.occupant_awareness-service.fuzzer",
+    static_libs: [
+        "android.hardware.automotive.occupant_awareness-V1-ndk",
+        "libbase",
+        "libbinder_random_parcel",
+        "libcutils",
+        "liblog",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder",
+        "libutils",
+    ],
+    srcs: [
+        "fuzzer.cpp",
+        "OccupantAwareness.cpp",
+    ],
+    fuzz_config: {
+        cc: [
+            "keithmok@google.com",
+        ],
+    },
+}
diff --git a/automotive/occupant_awareness/aidl/default/fuzzer.cpp b/automotive/occupant_awareness/aidl/default/fuzzer.cpp
new file mode 100644
index 0000000..551b83a
--- /dev/null
+++ b/automotive/occupant_awareness/aidl/default/fuzzer.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "OccupantAwareness.h"
+
+using ::aidl::android::hardware::automotive::occupant_awareness::IOccupantAwareness;
+using ::android::fuzzService;
+using ::android::hardware::automotive::occupant_awareness::V1_0::implementation::OccupantAwareness;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    auto occupantAwareness = SharedRefBase::make<OccupantAwareness>();
+
+    fuzzService(occupantAwareness->asBinder().get(), FuzzedDataProvider(data, size));
+
+    return 0;
+}
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/GeneratorHub.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/GeneratorHub.cpp
index 9be9ea7..503afd2 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/GeneratorHub.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/GeneratorHub.cpp
@@ -28,11 +28,18 @@
 
 namespace impl {
 
-GeneratorHub::GeneratorHub(const OnHalEvent& onHalEvent)
-    : mOnHalEvent(onHalEvent), mThread(&GeneratorHub::run, this) {}
+GeneratorHub::GeneratorHub(const OnHalEvent& onHalEvent) : mOnHalEvent(onHalEvent) {
+    mThread = std::thread(&GeneratorHub::run, this);
+}
 
 GeneratorHub::~GeneratorHub() {
-    mShuttingDownFlag.store(true);
+    {
+        // Even if the shared variable is atomic, it must be modified under the
+        // mutex in order to correctly publish the modification to the waiting
+        // thread.
+        std::unique_lock<std::mutex> g(mLock);
+        mShuttingDownFlag.store(true);
+    }
     mCond.notify_all();
     if (mThread.joinable()) {
         mThread.join();
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..f01444e 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) {
@@ -402,7 +402,7 @@
     gotValue->timestamp = 0;
 
     std::string infoMake = toString(*gotValue);
-    EXPECT_THAT(std::string(buf), HasSubstr(infoMake));
+    EXPECT_THAT(std::string(buf, sizeof(buf)), HasSubstr(infoMake));
 }
 
 TEST_F(DefaultVhalImplTest, testSetPropInvalidAreaId) {
diff --git a/automotive/vehicle/README.md b/automotive/vehicle/README.md
new file mode 100644
index 0000000..e0f03e2
--- /dev/null
+++ b/automotive/vehicle/README.md
@@ -0,0 +1,26 @@
+# Vehicle Hardware Abstraction Layer (VHAL)
+---
+
+This directory stores the VHAL interface definition and VHAL reference
+implementation.
+
+## 2.0 (deprecated)
+
+HIDL based VHAL interface and reference implementation. 
+
+## aidl
+
+AIDL based VHAL interfadce and reference implementation.
+
+## proto
+
+Protobuf used to pass message between emulator VHAL and emulator.
+
+## tools
+
+Dev tools related to VHAL.
+
+## vts
+
+VTS test for VHAL. The VTS test works for both AIDL and HIDL VHAL
+implementation. Vendor implementation of VHAL must passes VTS.
diff --git a/automotive/vehicle/aidl/README.md b/automotive/vehicle/aidl/README.md
new file mode 100644
index 0000000..09f03b4
--- /dev/null
+++ b/automotive/vehicle/aidl/README.md
@@ -0,0 +1,23 @@
+# AIDL Vehicle Hardware Abstraction Layer (VHAL)
+---
+
+This directory stores the AIDL VHAL interface and reference implementation.
+
+## aidl_api
+
+Auto-generated current and previous versions of AIDL VHAL api.
+
+## aidl_test
+
+Contains a test to test that all HIDL VHAL properties are supported in
+AIDL VHAL.
+
+## android
+
+Contains AIDL VHAL interface definition. The main interface file is
+`android/hardware/automotive/vehicle/IVehicle.aidl`.
+
+## impl
+
+Reference implementation for AIDL VHAL and useful libraries for implementing
+vendor AIDL VHAL.
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/GsrComplianceRequirementType.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/GsrComplianceRequirementType.aidl
new file mode 100644
index 0000000..0d3a061
--- /dev/null
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/GsrComplianceRequirementType.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.automotive.vehicle;
+@Backing(type="int") @VintfStability
+enum GsrComplianceRequirementType {
+  GSR_COMPLIANCE_NOT_REQUIRED = 0,
+  GSR_COMPLIANCE_REQUIRED_THROUGH_SYSTEM_IMAGE = 1,
+}
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleProperty.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleProperty.aidl
index 04f8fa3..2d0cde5 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleProperty.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleProperty.aidl
@@ -206,4 +206,5 @@
   EV_REGENERATIVE_BRAKING_STATE = 289410884,
   TRAILER_PRESENT = 289410885,
   VEHICLE_CURB_WEIGHT = 289410886,
+  GENERAL_SAFETY_REGULATION_COMPLIANCE_REQUIREMENT = 289410887,
 }
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/GsrComplianceRequirementType.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/GsrComplianceRequirementType.aidl
new file mode 100644
index 0000000..e0609d5
--- /dev/null
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/GsrComplianceRequirementType.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.automotive.vehicle;
+
+/**
+ * Used by GENERAL_SAFETY_REGULATION_COMPLIANCE_REQUIREMENT to indicate what
+ * kind of general safety regulation compliance requirement is enforced.
+ */
+@VintfStability
+@Backing(type="int")
+enum GsrComplianceRequirementType {
+    /**
+     * GSR compliance is not required.
+     */
+    GSR_COMPLIANCE_NOT_REQUIRED = 0,
+    /**
+     * GSR compliance is required through system image.
+     */
+    GSR_COMPLIANCE_REQUIRED_THROUGH_SYSTEM_IMAGE = 1,
+}
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl
index 727b949..15d8e58 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl
@@ -2840,4 +2840,17 @@
     VEHICLE_CURB_WEIGHT = 0x0F46 + 0x10000000 + 0x01000000
             + 0x00400000, // VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
 
+    /**
+     * EU's General security regulation compliance requirement.
+     *
+     * Returns whether general security regulation compliance is required, if
+     * so, what type of requirement.
+     *
+     * @change_mode VehiclePropertyChangeMode:STATIC
+     * @access VehiclePropertyAccess:READ
+     * @data_enum GsrComplianceRequirementType
+     */
+    GENERAL_SAFETY_REGULATION_COMPLIANCE_REQUIREMENT = 0x0F47 + 0x10000000 + 0x01000000
+            + 0x00400000, // VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
+
 }
diff --git a/automotive/vehicle/aidl/impl/Android.bp b/automotive/vehicle/aidl/impl/Android.bp
index d24a739..73f7df0 100644
--- a/automotive/vehicle/aidl/impl/Android.bp
+++ b/automotive/vehicle/aidl/impl/Android.bp
@@ -19,10 +19,16 @@
 }
 
 cc_defaults {
+    name: "VehicleHalInterfaceDefaults",
+    static_libs: [
+        "android.hardware.automotive.vehicle-V2-ndk",
+    ],
+}
+
+cc_defaults {
     name: "VehicleHalDefaults",
     static_libs: [
         "android-automotive-large-parcelable-lib",
-        "android.hardware.automotive.vehicle-V1-ndk",
         "libmath",
     ],
     shared_libs: [
@@ -37,6 +43,7 @@
         "-Wthread-safety",
     ],
     defaults: [
+        "VehicleHalInterfaceDefaults",
         "android-automotive-large-parcelable-defaults",
     ],
 }
diff --git a/automotive/vehicle/aidl/impl/README.md b/automotive/vehicle/aidl/impl/README.md
new file mode 100644
index 0000000..121ffd1
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/README.md
@@ -0,0 +1,58 @@
+# AIDL VHAL libraries and reference implementation.
+---
+
+This directory stores the libraries useful for implementing vendor AIDL VHAL.
+This directory also stores a reference fake implementation for AIDL VHAL.
+
+## default_config
+
+Stores the default vehicle property configurations for reference vehicle HAL.
+Vendor implementation could copy this library but must update the configuration
+to meet their own requirements, e.g. enable or disable certain properties or
+update the initial value for certain properties.
+
+##	fake_impl
+
+Contains libraries used specifically for the fake reference VHAL implementation.
+These libraries are for test only and must not be directly used for vendor
+VHAL implementation.
+
+These libraries contain test-spcific logic and must not run directly on a real
+vehicle.
+
+## grpc
+
+Stores code for GRPC based VHAL implementation.
+
+## hardware
+
+Defines an interface `IVehicleHardware.h` which vendor must implement for
+vehicle-specific logic if they want to follow our reference VHAL design.
+
+## proto
+
+Stores Some protobuf files translated from AIDL VHAL interface types. These
+files are used in GRPC VHAL implementation.
+
+## utils
+
+Defines a library `VehicleHalUtils` which provides useful utility functions for
+VHAL implementation. Vendor VHAL could use this library.
+
+## vhal
+
+Defines a library `DefaultVehicleHal` which provides generic logic for all VHAL
+implementations (including reference VHAL). Vendor VHAL implementation could
+use this library, along with their own implementation for `IVehicleHardware`
+interface.
+
+Also defines a binary `android.hardware.automotive.vehicle@V1-default-service`
+which is the reference VHAL implementation. It implements `IVehicle.aidl`
+interface. It uses `DefaultVehicleHal`, along with `FakeVehicleHardware`
+(in fake_impl). It simulates the vehicle bus interaction by using an
+in-memory map. Meaning that all properties (except for some special ones) are
+just written into a hash map and read from a hash map without relying on any
+hardware. As a result, the reference implementation can run on emulator or
+any host environment.
+
+Vendor must not directly use the reference implementation for a real vehicle.
\ No newline at end of file
diff --git a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
index ff480e9..409b932 100644
--- a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
+++ b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
@@ -36,6 +36,7 @@
 using ::aidl::android::hardware::automotive::vehicle::EvsServiceState;
 using ::aidl::android::hardware::automotive::vehicle::EvsServiceType;
 using ::aidl::android::hardware::automotive::vehicle::FuelType;
+using ::aidl::android::hardware::automotive::vehicle::GsrComplianceRequirementType;
 using ::aidl::android::hardware::automotive::vehicle::RawPropValues;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
@@ -460,6 +461,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,
@@ -836,7 +846,7 @@
         {.config = {.prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
                     .access = VehiclePropertyAccess::READ,
                     .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
-                    .configArray = {3}}},
+                    .configArray = {0}}},
 
         {.config = {.prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
                     .access = VehiclePropertyAccess::READ_WRITE,
@@ -1246,6 +1256,19 @@
                                 .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
                         },
         },
+        {
+                .config =
+                        {
+                                .prop = toInt(
+                                        VehicleProperty::
+                                                GENERAL_SAFETY_REGULATION_COMPLIANCE_REQUIREMENT),
+                                .access = VehiclePropertyAccess::READ,
+                                .changeMode = VehiclePropertyChangeMode::STATIC,
+                        },
+                .initialValue = {.int32Values = {toInt(
+                                         GsrComplianceRequirementType::
+                                                 GSR_COMPLIANCE_REQUIRED_THROUGH_SYSTEM_IMAGE)}},
+        },
 #ifdef ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
         // Vendor propetry for E2E ClusterHomeService testing.
         {
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/GeneratorHub.h b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/GeneratorHub.h
index 9f112ae..f96b6ec 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/GeneratorHub.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/GeneratorHub.h
@@ -54,8 +54,8 @@
     void registerGenerator(int32_t generatorId, std::unique_ptr<FakeValueGenerator> generator);
 
     // Unregister a generator with the generatorId. If no registered generator is found, this
-    // function does nothing.
-    void unregisterGenerator(int32_t generatorId);
+    // function does nothing. Returns true if the generator is unregistered.
+    bool unregisterGenerator(int32_t generatorId);
 
   private:
     struct VhalEvent {
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/JsonFakeValueGenerator.h b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/JsonFakeValueGenerator.h
index d421ac5..42b1bd3 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/JsonFakeValueGenerator.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/include/JsonFakeValueGenerator.h
@@ -45,6 +45,9 @@
     // Create a new JSON fake value generator using the specified JSON file path. All the events
     // in the JSON file would be generated once.
     explicit JsonFakeValueGenerator(const std::string& path);
+    // Create a new JSON fake value generator using the JSON content. The first argument is just
+    // used to differentiate this function with the one that takes path as input.
+    explicit JsonFakeValueGenerator(bool unused, const std::string& content, int32_t iteration);
 
     ~JsonFakeValueGenerator() = default;
 
@@ -53,6 +56,9 @@
     const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
     getAllEvents();
 
+    // Whether there are events left to replay for this generator.
+    bool hasNext();
+
   private:
     size_t mEventIndex = 0;
     std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue> mEvents;
@@ -60,7 +66,8 @@
     int32_t mNumOfIterations = 0;
 
     void setBit(std::vector<uint8_t>& bytes, size_t idx);
-    void init(const std::string& path, int32_t iteration);
+    void initWithPath(const std::string& path, int32_t iteration);
+    void initWithStream(std::istream& is, int32_t iteration);
 };
 
 }  // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/GeneratorHub.cpp b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/GeneratorHub.cpp
index 0c182d9..d815456 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/GeneratorHub.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/GeneratorHub.cpp
@@ -29,11 +29,18 @@
 
 using ::android::base::ScopedLockAssertion;
 
-GeneratorHub::GeneratorHub(OnHalEvent&& onHalEvent)
-    : mOnHalEvent(onHalEvent), mThread(&GeneratorHub::run, this) {}
+GeneratorHub::GeneratorHub(OnHalEvent&& onHalEvent) : mOnHalEvent(onHalEvent) {
+    mThread = std::thread(&GeneratorHub::run, this);
+}
 
 GeneratorHub::~GeneratorHub() {
-    mShuttingDownFlag.store(true);
+    {
+        // Even if the shared variable is atomic, it must be modified under the
+        // mutex in order to correctly publish the modification to the waiting
+        // thread.
+        std::unique_lock<std::mutex> lock(mGeneratorsLock);
+        mShuttingDownFlag.store(true);
+    }
     mCond.notify_all();
     if (mThread.joinable()) {
         mThread.join();
@@ -58,13 +65,15 @@
     mCond.notify_one();
 }
 
-void GeneratorHub::unregisterGenerator(int32_t id) {
+bool GeneratorHub::unregisterGenerator(int32_t id) {
+    bool removed;
     {
         std::scoped_lock<std::mutex> lockGuard(mGeneratorsLock);
-        mGenerators.erase(id);
+        removed = mGenerators.erase(id);
     }
     mCond.notify_one();
     ALOGI("%s: Unregistered generator, id: %d", __func__, id);
+    return removed;
 }
 
 void GeneratorHub::run() {
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp
index d4d52a5..cb42317 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp
@@ -173,12 +173,11 @@
 
 }  // namespace
 
-JsonFakeValueGenerator::JsonFakeValueGenerator(const std::string& path) {
-    init(path, 1);
-}
+JsonFakeValueGenerator::JsonFakeValueGenerator(const std::string& path)
+    : JsonFakeValueGenerator(path, /*iteration=*/1) {}
 
 JsonFakeValueGenerator::JsonFakeValueGenerator(const std::string& path, int32_t iteration) {
-    init(path, iteration);
+    initWithPath(path, iteration);
 }
 
 JsonFakeValueGenerator::JsonFakeValueGenerator(const VehiclePropValue& request) {
@@ -186,16 +185,26 @@
     // Iterate infinitely if iteration number is not provided
     int32_t numOfIterations = v.int32Values.size() < 2 ? -1 : v.int32Values[1];
 
-    init(v.stringValue, numOfIterations);
+    initWithPath(v.stringValue, numOfIterations);
 }
 
-void JsonFakeValueGenerator::init(const std::string& path, int32_t iteration) {
+JsonFakeValueGenerator::JsonFakeValueGenerator([[maybe_unused]] bool unused,
+                                               const std::string& content, int32_t iteration) {
+    std::istringstream iss(content);
+    initWithStream(iss, iteration);
+}
+
+void JsonFakeValueGenerator::initWithPath(const std::string& path, int32_t iteration) {
     std::ifstream ifs(path);
     if (!ifs) {
         ALOGE("%s: couldn't open %s for parsing.", __func__, path.c_str());
         return;
     }
-    mEvents = parseFakeValueJson(ifs);
+    initWithStream(ifs, iteration);
+}
+
+void JsonFakeValueGenerator::initWithStream(std::istream& is, int32_t iteration) {
+    mEvents = parseFakeValueJson(is);
     mNumOfIterations = iteration;
 }
 
@@ -235,12 +244,15 @@
             mNumOfIterations--;
         }
     }
-
     generatedValue.timestamp = mLastEventTimestamp;
 
     return generatedValue;
 }
 
+bool JsonFakeValueGenerator::hasNext() {
+    return mNumOfIterations != 0 && mEvents.size() > 0;
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
diff --git a/automotive/vehicle/aidl/impl/fake_impl/README.md b/automotive/vehicle/aidl/impl/fake_impl/README.md
new file mode 100644
index 0000000..e86dcee
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/README.md
@@ -0,0 +1,28 @@
+# Fake reference AIDL VHAL implementation libraries
+---
+
+This directory stores libraries for implementing a fake reference AIDL VHAL.
+
+WARNING: All the libraries here are for TEST ONLY.
+
+## GeneratorHub
+
+Defines a library `FakeVehicleHalValueGenerators` that could generate fake
+vehicle property values for testing.
+
+## hardware
+
+Defines a fake implementation for device-specifc interface `IVehicleHardware`:
+`FakeVehicleHardware`. This implementation uses a in-memory map for storing
+property values and does not communicate with or depending on any specific
+vehicle bus.
+
+## obd2frame
+
+Defines a library `FakeObd2Frame` that generates fake OBD2 frame for OBD2
+properties.
+
+## userhal
+
+Defines a library `FakeUserHal` that emulates a real User HAL behavior by
+parsing debug commands.
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
index 34b2b24..8cc19b1 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -21,10 +21,12 @@
 #include <DefaultConfig.h>
 #include <FakeObd2Frame.h>
 #include <FakeUserHal.h>
+#include <GeneratorHub.h>
 #include <IVehicleHardware.h>
 #include <RecurrentTimer.h>
 #include <VehicleHalTypes.h>
 #include <VehiclePropertyStore.h>
+#include <aidl/android/hardware/automotive/vehicle/VehicleHwKeyInputAction.h>
 #include <android-base/parseint.h>
 #include <android-base/result.h>
 #include <android-base/stringprintf.h>
@@ -132,11 +134,15 @@
     const std::unique_ptr<FakeUserHal> mFakeUserHal;
     // RecurrentTimer is thread-safe.
     std::unique_ptr<RecurrentTimer> mRecurrentTimer;
+    // GeneratorHub is thread-safe.
+    std::unique_ptr<GeneratorHub> mGeneratorHub;
     std::mutex mLock;
     std::unique_ptr<const PropertyChangeCallback> mOnPropertyChangeCallback GUARDED_BY(mLock);
     std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback GUARDED_BY(mLock);
     std::unordered_map<PropIdAreaId, std::shared_ptr<RecurrentTimer::Callback>, PropIdAreaIdHash>
             mRecurrentActions GUARDED_BY(mLock);
+    std::unordered_map<PropIdAreaId, VehiclePropValuePool::RecyclableType, PropIdAreaIdHash>
+            mSavedProps GUARDED_BY(mLock);
     // PendingRequestHandler is thread-safe.
     mutable PendingRequestHandler<GetValuesCallback,
                                   aidl::android::hardware::automotive::vehicle::GetValueRequest>
@@ -156,6 +162,10 @@
     void maybeOverrideProperties(const char* overrideDir);
     // Override the properties using config files in 'overrideDir'.
     void overrideProperties(const char* overrideDir);
+    // Function to be called when a value change event comes from vehicle bus. In our fake
+    // implementation, this function is only called during "--inject-event" dump command.
+    void eventFromVehicleBus(
+            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
 
     VhalResult<void> maybeSetSpecialValue(
             const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value,
@@ -184,6 +194,10 @@
     std::string dumpListProperties();
     std::string dumpSpecificProperty(const std::vector<std::string>& options);
     std::string dumpSetProperties(const std::vector<std::string>& options);
+    std::string dumpGetPropertyWithArg(const std::vector<std::string>& options);
+    std::string dumpSaveProperty(const std::vector<std::string>& options);
+    std::string dumpRestoreProperty(const std::vector<std::string>& options);
+    std::string dumpInjectEvent(const std::vector<std::string>& options);
 
     template <typename T>
     android::base::Result<T> safelyParseInt(int index, const std::string& s) {
@@ -198,7 +212,7 @@
     std::vector<std::string> getOptionValues(const std::vector<std::string>& options,
                                              size_t* index);
     android::base::Result<aidl::android::hardware::automotive::vehicle::VehiclePropValue>
-    parseSetPropOptions(const std::vector<std::string>& options);
+    parsePropOptions(const std::vector<std::string>& options);
     android::base::Result<std::vector<uint8_t>> parseHexString(const std::string& s);
 
     android::base::Result<void> checkArgumentsSize(const std::vector<std::string>& options,
@@ -207,6 +221,14 @@
             const aidl::android::hardware::automotive::vehicle::GetValueRequest& request);
     aidl::android::hardware::automotive::vehicle::SetValueResult handleSetValueRequest(
             const aidl::android::hardware::automotive::vehicle::SetValueRequest& request);
+
+    std::string genFakeDataCommand(const std::vector<std::string>& options);
+
+    static aidl::android::hardware::automotive::vehicle::VehiclePropValue createHwInputKeyProp(
+            aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction action,
+            int32_t keyCode, int32_t targetDisplay);
+    static std::string genFakeDataHelp();
+    static std::string parseErrMsg(std::string fieldName, std::string value, std::string type);
 };
 
 }  // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
index b64c1a6..a0fda79 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -22,6 +22,7 @@
 #include <DefaultConfig.h>
 #include <FakeObd2Frame.h>
 #include <JsonFakeValueGenerator.h>
+#include <LinearFakeValueGenerator.h>
 #include <PropertyUtils.h>
 #include <TestPropertyUtils.h>
 #include <VehicleHalTypes.h>
@@ -33,6 +34,7 @@
 #include <utils/SystemClock.h>
 
 #include <dirent.h>
+#include <inttypes.h>
 #include <sys/types.h>
 #include <fstream>
 #include <regex>
@@ -55,6 +57,7 @@
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
+using ::aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
 using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyGroup;
@@ -64,6 +67,7 @@
 
 using ::android::base::EqualsIgnoreCase;
 using ::android::base::Error;
+using ::android::base::GetIntProperty;
 using ::android::base::ParseFloat;
 using ::android::base::Result;
 using ::android::base::ScopedLockAssertion;
@@ -72,6 +76,7 @@
 
 const char* VENDOR_OVERRIDE_DIR = "/vendor/etc/automotive/vhaloverride/";
 const char* OVERRIDE_PROPERTY = "persist.vendor.vhal_init_value_override";
+const char* POWER_STATE_REQ_CONFIG_PROPERTY = "ro.vendor.fake_vhal.ap_power_state_req.config";
 
 // A list of supported options for "--set" command.
 const std::unordered_set<std::string> SET_PROP_OPTIONS = {
@@ -86,7 +91,9 @@
         // bytes in hex format, e.g. 0xDEADBEEF.
         "-b",
         // Area id in integer.
-        "-a"};
+        "-a",
+        // Timestamp in int64.
+        "-t"};
 
 }  // namespace
 
@@ -140,6 +147,8 @@
       mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
       mFakeUserHal(new FakeUserHal(mValuePool)),
       mRecurrentTimer(new RecurrentTimer()),
+      mGeneratorHub(new GeneratorHub(
+              [this](const VehiclePropValue& value) { eventFromVehicleBus(value); })),
       mPendingGetValueRequests(this),
       mPendingSetValueRequests(this) {
     init();
@@ -148,6 +157,7 @@
 FakeVehicleHardware::~FakeVehicleHardware() {
     mPendingGetValueRequests.stop();
     mPendingSetValueRequests.stop();
+    mGeneratorHub.reset();
 }
 
 void FakeVehicleHardware::init() {
@@ -155,7 +165,10 @@
         VehiclePropConfig cfg = it.config;
         VehiclePropertyStore::TokenFunction tokenFunction = nullptr;
 
-        if (cfg.prop == OBD2_FREEZE_FRAME) {
+        if (cfg.prop == toInt(VehicleProperty::AP_POWER_STATE_REQ)) {
+            int config = GetIntProperty(POWER_STATE_REQ_CONFIG_PROPERTY, /*default_value=*/0);
+            cfg.configArray[0] = config;
+        } else if (cfg.prop == OBD2_FREEZE_FRAME) {
             tokenFunction = [](const VehiclePropValue& propValue) { return propValue.timestamp; };
         }
 
@@ -575,34 +588,237 @@
         result.buffer = dumpListProperties();
     } else if (EqualsIgnoreCase(option, "--get")) {
         result.buffer = dumpSpecificProperty(options);
+    } else if (EqualsIgnoreCase(option, "--getWithArg")) {
+        result.buffer = dumpGetPropertyWithArg(options);
     } else if (EqualsIgnoreCase(option, "--set")) {
         result.buffer = dumpSetProperties(options);
+    } else if (EqualsIgnoreCase(option, "--save-prop")) {
+        result.buffer = dumpSaveProperty(options);
+    } else if (EqualsIgnoreCase(option, "--restore-prop")) {
+        result.buffer = dumpRestoreProperty(options);
+    } else if (EqualsIgnoreCase(option, "--inject-event")) {
+        result.buffer = dumpInjectEvent(options);
     } else if (EqualsIgnoreCase(option, kUserHalDumpOption)) {
         if (options.size() == 1) {
             result.buffer = mFakeUserHal->showDumpHelp();
         } else {
             result.buffer = mFakeUserHal->dump(options[1]);
         }
+    } else if (EqualsIgnoreCase(option, "--genfakedata")) {
+        result.buffer = genFakeDataCommand(options);
     } else {
         result.buffer = StringPrintf("Invalid option: %s\n", option.c_str());
     }
     return result;
 }
 
+std::string FakeVehicleHardware::genFakeDataHelp() {
+    return R"(
+Generate Fake Data Usage:
+--genfakedata --startlinear [propID] [mValue] [cValue] [dispersion] [increment] [interval]: "
+Start a linear generator that generates event with floatValue within range:
+[mValue - disperson, mValue + dispersion].
+propID(int32): ID for the property to generate event for.
+mValue(float): The middle of the possible values for the property.
+cValue(float): The start value for the property, must be within the range.
+dispersion(float): The range the value can change.
+increment(float): The step the value would increase by for each generated event,
+if exceed the range, the value would loop back.
+interval(int64): The interval in nanoseconds the event would generate by.
+
+--genfakedata --stoplinear [propID(int32)]: Stop a linear generator
+
+--genfakedata --startjson --path [jsonFilePath] [repetition]:
+Start a JSON generator that would generate events according to a JSON file.
+jsonFilePath(string): The path to a JSON file. The JSON content must be in the format of
+[{
+    "timestamp": 1000000,
+    "areaId": 0,
+    "value": 8,
+    "prop": 289408000
+}, {...}]
+Each event in the JSON file would be generated by the same interval their timestamp is relative to
+the first event's timestamp.
+repetition(int32, optional): how many iterations the events would be generated. If it is not
+provided, it would iterate indefinitely.
+
+--genfakedata --startjson --content [jsonContent]: Start a JSON generator using the content.
+
+--genfakedata --stopjson [generatorID(string)]: Stop a JSON generator.
+
+--genfakedata --keypress [keyCode(int32)] [display[int32]]: Generate key press.
+
+)";
+}
+
+std::string FakeVehicleHardware::parseErrMsg(std::string fieldName, std::string value,
+                                             std::string type) {
+    return StringPrintf("failed to parse %s as %s: \"%s\"\n%s", fieldName.c_str(), type.c_str(),
+                        value.c_str(), genFakeDataHelp().c_str());
+}
+
+std::string FakeVehicleHardware::genFakeDataCommand(const std::vector<std::string>& options) {
+    if (options.size() < 2) {
+        return "No subcommand specified for genfakedata\n" + genFakeDataHelp();
+    }
+
+    std::string command = options[1];
+    if (command == "--startlinear") {
+        // --genfakedata --startlinear [propID(int32)] [middleValue(float)]
+        // [currentValue(float)] [dispersion(float)] [increment(float)] [interval(int64)]
+        if (options.size() != 8) {
+            return "incorrect argument count, need 8 arguments for --genfakedata --startlinear\n" +
+                   genFakeDataHelp();
+        }
+        int32_t propId;
+        float middleValue;
+        float currentValue;
+        float dispersion;
+        float increment;
+        int64_t interval;
+        if (!android::base::ParseInt(options[2], &propId)) {
+            return parseErrMsg("propId", options[2], "int");
+        }
+        if (!android::base::ParseFloat(options[3], &middleValue)) {
+            return parseErrMsg("middleValue", options[3], "float");
+        }
+        if (!android::base::ParseFloat(options[4], &currentValue)) {
+            return parseErrMsg("currentValue", options[4], "float");
+        }
+        if (!android::base::ParseFloat(options[5], &dispersion)) {
+            return parseErrMsg("dispersion", options[5], "float");
+        }
+        if (!android::base::ParseFloat(options[6], &increment)) {
+            return parseErrMsg("increment", options[6], "float");
+        }
+        if (!android::base::ParseInt(options[7], &interval)) {
+            return parseErrMsg("interval", options[7], "int");
+        }
+        auto generator = std::make_unique<LinearFakeValueGenerator>(
+                propId, middleValue, currentValue, dispersion, increment, interval);
+        mGeneratorHub->registerGenerator(propId, std::move(generator));
+        return "Linear event generator started successfully";
+    } else if (command == "--stoplinear") {
+        // --genfakedata --stoplinear [propID(int32)]
+        if (options.size() != 3) {
+            return "incorrect argument count, need 3 arguments for --genfakedata --stoplinear\n" +
+                   genFakeDataHelp();
+        }
+        int32_t propId;
+        if (!android::base::ParseInt(options[2], &propId)) {
+            return parseErrMsg("propId", options[2], "int");
+        }
+        if (mGeneratorHub->unregisterGenerator(propId)) {
+            return "Linear event generator stopped successfully";
+        }
+        return StringPrintf("No linear event generator found for property: %d", propId);
+    } else if (command == "--startjson") {
+        // --genfakedata --startjson --path path repetition
+        // or
+        // --genfakedata --startjson --content content repetition.
+        if (options.size() != 4 && options.size() != 5) {
+            return "incorrect argument count, need 4 or 5 arguments for --genfakedata "
+                   "--startjson\n";
+        }
+        // Iterate infinitely if repetition number is not provided
+        int32_t repetition = -1;
+        if (options.size() == 5) {
+            if (!android::base::ParseInt(options[4], &repetition)) {
+                return parseErrMsg("repetition", options[4], "int");
+            }
+        }
+        std::unique_ptr<JsonFakeValueGenerator> generator;
+        if (options[2] == "--path") {
+            const std::string& fileName = options[3];
+            generator = std::make_unique<JsonFakeValueGenerator>(fileName, repetition);
+            if (!generator->hasNext()) {
+                return "invalid JSON file, no events";
+            }
+        } else if (options[2] == "--content") {
+            const std::string& content = options[3];
+            generator =
+                    std::make_unique<JsonFakeValueGenerator>(/*unused=*/true, content, repetition);
+            if (!generator->hasNext()) {
+                return "invalid JSON content, no events";
+            }
+        }
+        int32_t cookie = std::hash<std::string>()(options[3]);
+        mGeneratorHub->registerGenerator(cookie, std::move(generator));
+        return StringPrintf("JSON event generator started successfully, ID: %" PRId32, cookie);
+    } else if (command == "--stopjson") {
+        // --genfakedata --stopjson [generatorID(string)]
+        if (options.size() != 3) {
+            return "incorrect argument count, need 3 arguments for --genfakedata --stopjson\n";
+        }
+        int32_t cookie;
+        if (!android::base::ParseInt(options[2], &cookie)) {
+            return parseErrMsg("cookie", options[2], "int");
+        }
+        if (mGeneratorHub->unregisterGenerator(cookie)) {
+            return "JSON event generator stopped successfully";
+        } else {
+            return StringPrintf("No JSON event generator found for ID: %s", options[2].c_str());
+        }
+    } else if (command == "--keypress") {
+        int32_t keyCode;
+        int32_t display;
+        // --genfakedata --keypress [keyCode(int32)] [display[int32]]
+        if (options.size() != 4) {
+            return "incorrect argument count, need 4 arguments for --genfakedata --keypress\n";
+        }
+        if (!android::base::ParseInt(options[2], &keyCode)) {
+            return parseErrMsg("keyCode", options[2], "int");
+        }
+        if (!android::base::ParseInt(options[3], &display)) {
+            return parseErrMsg("display", options[3], "int");
+        }
+        // Send back to HAL
+        onValueChangeCallback(
+                createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_DOWN, keyCode, display));
+        onValueChangeCallback(
+                createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_UP, keyCode, display));
+        return "keypress event generated successfully";
+    }
+
+    return StringPrintf("Unknown command: \"%s\"\n%s", command.c_str(), genFakeDataHelp().c_str());
+}
+
+VehiclePropValue FakeVehicleHardware::createHwInputKeyProp(VehicleHwKeyInputAction action,
+                                                           int32_t keyCode, int32_t targetDisplay) {
+    VehiclePropValue value = {
+            .prop = toInt(VehicleProperty::HW_KEY_INPUT),
+            .areaId = 0,
+            .timestamp = elapsedRealtimeNano(),
+            .status = VehiclePropertyStatus::AVAILABLE,
+            .value.int32Values = {toInt(action), keyCode, targetDisplay},
+    };
+    return value;
+}
+
+void FakeVehicleHardware::eventFromVehicleBus(const VehiclePropValue& value) {
+    mServerSidePropStore->writeValue(mValuePool->obtain(value));
+}
+
 std::string FakeVehicleHardware::dumpHelp() {
     return "Usage: \n\n"
            "[no args]: dumps (id and value) all supported properties \n"
            "--help: shows this help\n"
            "--list: lists the ids of all supported properties\n"
-           "--get <PROP1> [PROP2] [PROPN]: dumps the value of specific properties \n"
-           "--set <PROP> [-i INT_VALUE [INT_VALUE ...]] [-i64 INT64_VALUE [INT64_VALUE ...]] "
-           "[-f FLOAT_VALUE [FLOAT_VALUE ...]] [-s STR_VALUE] "
-           "[-b BYTES_VALUE] [-a AREA_ID] : sets the value of property PROP. "
+           "--get <PROP1> [PROP2] [PROPN]: dumps the value of specific properties. \n"
+           "--getWithArg <PROP> [ValueArguments]: gets the value for a specific property with "
+           "arguments. \n"
+           "--set <PROP> [ValueArguments]: sets the value of property PROP. \n"
+           "--save-prop <prop> [-a AREA_ID]: saves the current value for PROP, integration test"
+           " that modifies prop value must call this before test and restore-prop after test. \n"
+           "--restore-prop <prop> [-a AREA_ID]: restores a previously saved property value. \n"
+           "--inject-event <PROP> [ValueArguments]: inject a property update event from car\n\n"
+           "ValueArguments are in the format of [-i INT_VALUE [INT_VALUE ...]] "
+           "[-i64 INT64_VALUE [INT64_VALUE ...]] [-f FLOAT_VALUE [FLOAT_VALUE ...]] [-s STR_VALUE] "
+           "[-b BYTES_VALUE] [-a AREA_ID].\n"
            "Notice that the string, bytes and area value can be set just once, while the other can"
            " have multiple values (so they're used in the respective array), "
-           "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n\n"
-           "Fake user HAL usage: \n" +
-           mFakeUserHal->showDumpHelp();
+           "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n" +
+           genFakeDataHelp() + "Fake user HAL usage: \n" + mFakeUserHal->showDumpHelp();
 }
 
 std::string FakeVehicleHardware::dumpAllProperties() {
@@ -720,10 +936,11 @@
     return std::move(values);
 }
 
-Result<VehiclePropValue> FakeVehicleHardware::parseSetPropOptions(
+Result<VehiclePropValue> FakeVehicleHardware::parsePropOptions(
         const std::vector<std::string>& options) {
     // Options format:
-    // --set PROP [-f f1 f2...] [-i i1 i2...] [-i64 i1 i2...] [-s s1 s2...] [-b b1 b2...] [-a a]
+    // --set/get/inject-event PROP [-f f1 f2...] [-i i1 i2...] [-i64 i1 i2...] [-s s1 s2...]
+    // [-b b1 b2...] [-a a] [-t timestamp]
     size_t optionIndex = 1;
     auto result = safelyParseInt<int32_t>(optionIndex, options[optionIndex]);
     if (!result.ok()) {
@@ -737,83 +954,98 @@
     std::unordered_set<std::string> parsedOptions;
 
     while (optionIndex < options.size()) {
-        std::string type = options[optionIndex];
+        std::string argType = options[optionIndex];
         optionIndex++;
+
         size_t currentIndex = optionIndex;
-        std::vector<std::string> values = getOptionValues(options, &optionIndex);
-        if (parsedOptions.find(type) != parsedOptions.end()) {
-            return Error() << StringPrintf("Duplicate \"%s\" options\n", type.c_str());
+        std::vector<std::string> argValues = getOptionValues(options, &optionIndex);
+        if (parsedOptions.find(argType) != parsedOptions.end()) {
+            return Error() << StringPrintf("Duplicate \"%s\" options\n", argType.c_str());
         }
-        parsedOptions.insert(type);
-        if (EqualsIgnoreCase(type, "-i")) {
-            if (values.size() == 0) {
+        parsedOptions.insert(argType);
+        size_t argValuesSize = argValues.size();
+        if (EqualsIgnoreCase(argType, "-i")) {
+            if (argValuesSize == 0) {
                 return Error() << "No values specified when using \"-i\"\n";
             }
-            prop.value.int32Values.resize(values.size());
-            for (size_t i = 0; i < values.size(); i++) {
-                auto int32Result = safelyParseInt<int32_t>(currentIndex + i, values[i]);
+            prop.value.int32Values.resize(argValuesSize);
+            for (size_t i = 0; i < argValuesSize; i++) {
+                auto int32Result = safelyParseInt<int32_t>(currentIndex + i, argValues[i]);
                 if (!int32Result.ok()) {
                     return Error()
                            << StringPrintf("Value: \"%s\" is not a valid int: %s\n",
-                                           values[i].c_str(), getErrorMsg(int32Result).c_str());
+                                           argValues[i].c_str(), getErrorMsg(int32Result).c_str());
                 }
                 prop.value.int32Values[i] = int32Result.value();
             }
-        } else if (EqualsIgnoreCase(type, "-i64")) {
-            if (values.size() == 0) {
+        } else if (EqualsIgnoreCase(argType, "-i64")) {
+            if (argValuesSize == 0) {
                 return Error() << "No values specified when using \"-i64\"\n";
             }
-            prop.value.int64Values.resize(values.size());
-            for (size_t i = 0; i < values.size(); i++) {
-                auto int64Result = safelyParseInt<int64_t>(currentIndex + i, values[i]);
+            prop.value.int64Values.resize(argValuesSize);
+            for (size_t i = 0; i < argValuesSize; i++) {
+                auto int64Result = safelyParseInt<int64_t>(currentIndex + i, argValues[i]);
                 if (!int64Result.ok()) {
                     return Error()
                            << StringPrintf("Value: \"%s\" is not a valid int64: %s\n",
-                                           values[i].c_str(), getErrorMsg(int64Result).c_str());
+                                           argValues[i].c_str(), getErrorMsg(int64Result).c_str());
                 }
                 prop.value.int64Values[i] = int64Result.value();
             }
-        } else if (EqualsIgnoreCase(type, "-f")) {
-            if (values.size() == 0) {
+        } else if (EqualsIgnoreCase(argType, "-f")) {
+            if (argValuesSize == 0) {
                 return Error() << "No values specified when using \"-f\"\n";
             }
-            prop.value.floatValues.resize(values.size());
-            for (size_t i = 0; i < values.size(); i++) {
-                auto floatResult = safelyParseFloat(currentIndex + i, values[i]);
+            prop.value.floatValues.resize(argValuesSize);
+            for (size_t i = 0; i < argValuesSize; i++) {
+                auto floatResult = safelyParseFloat(currentIndex + i, argValues[i]);
                 if (!floatResult.ok()) {
                     return Error()
                            << StringPrintf("Value: \"%s\" is not a valid float: %s\n",
-                                           values[i].c_str(), getErrorMsg(floatResult).c_str());
+                                           argValues[i].c_str(), getErrorMsg(floatResult).c_str());
                 }
                 prop.value.floatValues[i] = floatResult.value();
             }
-        } else if (EqualsIgnoreCase(type, "-s")) {
-            if (values.size() != 1) {
+        } else if (EqualsIgnoreCase(argType, "-s")) {
+            if (argValuesSize != 1) {
                 return Error() << "Expect exact one value when using \"-s\"\n";
             }
-            prop.value.stringValue = values[0];
-        } else if (EqualsIgnoreCase(type, "-b")) {
-            if (values.size() != 1) {
+            prop.value.stringValue = argValues[0];
+        } else if (EqualsIgnoreCase(argType, "-b")) {
+            if (argValuesSize != 1) {
                 return Error() << "Expect exact one value when using \"-b\"\n";
             }
-            auto bytesResult = parseHexString(values[0]);
+            auto bytesResult = parseHexString(argValues[0]);
             if (!bytesResult.ok()) {
                 return Error() << StringPrintf("value: \"%s\" is not a valid hex string: %s\n",
-                                               values[0].c_str(), getErrorMsg(bytesResult).c_str());
+                                               argValues[0].c_str(),
+                                               getErrorMsg(bytesResult).c_str());
             }
             prop.value.byteValues = std::move(bytesResult.value());
-        } else if (EqualsIgnoreCase(type, "-a")) {
-            if (values.size() != 1) {
+        } else if (EqualsIgnoreCase(argType, "-a")) {
+            if (argValuesSize != 1) {
                 return Error() << "Expect exact one value when using \"-a\"\n";
             }
-            auto int32Result = safelyParseInt<int32_t>(currentIndex, values[0]);
+            auto int32Result = safelyParseInt<int32_t>(currentIndex, argValues[0]);
             if (!int32Result.ok()) {
                 return Error() << StringPrintf("Area ID: \"%s\" is not a valid int: %s\n",
-                                               values[0].c_str(), getErrorMsg(int32Result).c_str());
+                                               argValues[0].c_str(),
+                                               getErrorMsg(int32Result).c_str());
             }
             prop.areaId = int32Result.value();
+        } else if (EqualsIgnoreCase(argType, "-t")) {
+            if (argValuesSize != 1) {
+                return Error() << "Expect exact one value when using \"-t\"\n";
+            }
+            auto int64Result = safelyParseInt<int64_t>(currentIndex, argValues[0]);
+            if (!int64Result.ok()) {
+                return Error() << StringPrintf("Timestamp: \"%s\" is not a valid int64: %s\n",
+                                               argValues[0].c_str(),
+                                               getErrorMsg(int64Result).c_str());
+            }
+            prop.timestamp = int64Result.value();
         } else {
-            return Error() << StringPrintf("Unknown option: %s\n", type.c_str());
+            return Error() << StringPrintf("Unknown option: %s\n", argType.c_str());
         }
     }
 
@@ -825,7 +1057,7 @@
         return getErrorMsg(result);
     }
 
-    auto parseResult = parseSetPropOptions(options);
+    auto parseResult = parsePropOptions(options);
     if (!parseResult.ok()) {
         return getErrorMsg(parseResult);
     }
@@ -848,6 +1080,123 @@
                         getErrorMsg(setResult).c_str());
 }
 
+std::string FakeVehicleHardware::dumpGetPropertyWithArg(const std::vector<std::string>& options) {
+    if (auto result = checkArgumentsSize(options, 3); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    auto parseResult = parsePropOptions(options);
+    if (!parseResult.ok()) {
+        return getErrorMsg(parseResult);
+    }
+    VehiclePropValue prop = std::move(parseResult.value());
+    ALOGD("Dump: Getting property: %s", prop.toString().c_str());
+
+    bool isSpecialValue = false;
+    auto result = maybeGetSpecialValue(prop, &isSpecialValue);
+
+    if (!isSpecialValue) {
+        result = mServerSidePropStore->readValue(prop);
+    }
+
+    if (!result.ok()) {
+        return StringPrintf("failed to read property value: %d, error: %s, code: %d\n", prop.prop,
+                            getErrorMsg(result).c_str(), getIntErrorCode(result));
+    }
+    return StringPrintf("Get property result: %s\n", result.value()->toString().c_str());
+}
+
+std::string FakeVehicleHardware::dumpSaveProperty(const std::vector<std::string>& options) {
+    // Format: --save-prop PROP [-a areaID]
+    if (auto result = checkArgumentsSize(options, 2); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    auto parseResult = parsePropOptions(options);
+    if (!parseResult.ok()) {
+        return getErrorMsg(parseResult);
+    }
+    // We are only using the prop and areaId option.
+    VehiclePropValue value = std::move(parseResult.value());
+    int32_t propId = value.prop;
+    int32_t areaId = value.areaId;
+
+    auto readResult = mServerSidePropStore->readValue(value);
+    if (!readResult.ok()) {
+        return StringPrintf("Failed to save current property value, error: %s",
+                            getErrorMsg(readResult).c_str());
+    }
+
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    mSavedProps[PropIdAreaId{
+            .propId = propId,
+            .areaId = areaId,
+    }] = std::move(readResult.value());
+
+    return StringPrintf("Property: %" PRId32 ", areaID: %" PRId32 " saved", propId, areaId);
+}
+
+std::string FakeVehicleHardware::dumpRestoreProperty(const std::vector<std::string>& options) {
+    // Format: --restore-prop PROP [-a areaID]
+    if (auto result = checkArgumentsSize(options, 2); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    auto parseResult = parsePropOptions(options);
+    if (!parseResult.ok()) {
+        return getErrorMsg(parseResult);
+    }
+    // We are only using the prop and areaId option.
+    VehiclePropValue value = std::move(parseResult.value());
+    int32_t propId = value.prop;
+    int32_t areaId = value.areaId;
+    VehiclePropValuePool::RecyclableType savedValue;
+
+    {
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+        auto it = mSavedProps.find(PropIdAreaId{
+                .propId = propId,
+                .areaId = areaId,
+        });
+        if (it == mSavedProps.end()) {
+            return StringPrintf("No saved property for property: %" PRId32 ", areaID: %" PRId32,
+                                propId, areaId);
+        }
+
+        savedValue = std::move(it->second);
+        // Remove the saved property after restoring it.
+        mSavedProps.erase(it);
+    }
+
+    // Update timestamp.
+    savedValue->timestamp = elapsedRealtimeNano();
+
+    auto writeResult = mServerSidePropStore->writeValue(std::move(savedValue));
+    if (!writeResult.ok()) {
+        return StringPrintf("Failed to restore property value, error: %s",
+                            getErrorMsg(writeResult).c_str());
+    }
+
+    return StringPrintf("Property: %" PRId32 ", areaID: %" PRId32 " restored", propId, areaId);
+}
+
+std::string FakeVehicleHardware::dumpInjectEvent(const std::vector<std::string>& options) {
+    if (auto result = checkArgumentsSize(options, 3); !result.ok()) {
+        return getErrorMsg(result);
+    }
+
+    auto parseResult = parsePropOptions(options);
+    if (!parseResult.ok()) {
+        return getErrorMsg(parseResult);
+    }
+    VehiclePropValue prop = std::move(parseResult.value());
+    ALOGD("Dump: Injecting event from vehicle bus: %s", prop.toString().c_str());
+
+    eventFromVehicleBus(prop);
+
+    return StringPrintf("Event for property: %d injected", prop.prop);
+}
+
 StatusCode FakeVehicleHardware::checkHealth() {
     // Always return OK for checkHealth.
     return StatusCode::OK;
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp
index 90d1516..cfd6577 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp
@@ -42,6 +42,7 @@
     ],
     data: [
         ":FakeVehicleHardwareTestOverrideJson",
+        ":FakeVehicleHardwareTestPropJson",
     ],
     defaults: ["VehicleHalDefaults"],
     test_suites: ["device-tests"],
@@ -51,3 +52,8 @@
     name: "FakeVehicleHardwareTestOverrideJson",
     srcs: ["override/*"],
 }
+
+filegroup {
+    name: "FakeVehicleHardwareTestPropJson",
+    srcs: ["prop.json"],
+}
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
index 3e8f634..ab6bf51 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -53,6 +53,7 @@
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
+using ::aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
 using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
@@ -64,6 +65,7 @@
 using ::testing::ContainerEq;
 using ::testing::ContainsRegex;
 using ::testing::Eq;
+using ::testing::HasSubstr;
 using ::testing::WhenSortedBy;
 
 using std::chrono::milliseconds;
@@ -87,6 +89,7 @@
 class FakeVehicleHardwareTest : public ::testing::Test {
   protected:
     void SetUp() override {
+        mHardware = std::make_unique<FakeVehicleHardware>();
         auto callback = std::make_unique<IVehicleHardware::PropertyChangeCallback>(
                 [this](const std::vector<VehiclePropValue>& values) {
                     onPropertyChangeEvent(values);
@@ -98,7 +101,13 @@
                 [this](std::vector<GetValueResult> results) { onGetValues(results); });
     }
 
-    FakeVehicleHardware* getHardware() { return &mHardware; }
+    void TearDown() override {
+        // mHardware uses callback which contains reference to 'this', so it has to be destroyed
+        // before 'this'.
+        mHardware.reset();
+    }
+
+    FakeVehicleHardware* getHardware() { return mHardware.get(); }
 
     StatusCode setValues(const std::vector<SetValueRequest>& requests) {
         {
@@ -251,6 +260,14 @@
         return mChangedProperties;
     }
 
+    bool waitForChangedProperties(size_t count, milliseconds timeout) {
+        std::unique_lock<std::mutex> lk(mLock);
+        return mCv.wait_for(lk, timeout, [this, count] {
+            ScopedLockAssertion lockAssertion(mLock);
+            return mChangedProperties.size() >= count;
+        });
+    }
+
     bool waitForChangedProperties(int32_t propId, int32_t areaId, size_t count,
                                   milliseconds timeout) {
         PropIdAreaId propIdAreaId{
@@ -270,6 +287,15 @@
         mChangedProperties.clear();
     }
 
+    size_t getEventCount(int32_t propId, int32_t areaId) {
+        PropIdAreaId propIdAreaId{
+                .propId = propId,
+                .areaId = areaId,
+        };
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+        return mEventCount[propIdAreaId];
+    }
+
     static void addSetValueRequest(std::vector<SetValueRequest>& requests,
                                    std::vector<SetValueResult>& expectedResults, int64_t requestId,
                                    const VehiclePropValue& value, StatusCode expectedStatus) {
@@ -332,7 +358,7 @@
     } mPropValueCmp;
 
   private:
-    FakeVehicleHardware mHardware;
+    std::unique_ptr<FakeVehicleHardware> mHardware;
     std::shared_ptr<IVehicleHardware::SetValuesCallback> mSetValuesCallback;
     std::shared_ptr<IVehicleHardware::GetValuesCallback> mGetValuesCallback;
     std::condition_variable mCv;
@@ -1395,6 +1421,85 @@
     ASSERT_THAT(result.buffer, ContainsRegex("Invalid number of arguments"));
 }
 
+TEST_F(FakeVehicleHardwareTest, testDumpSpecificPropertyWithArg) {
+    auto getValueResult = getValue(VehiclePropValue{.prop = OBD2_FREEZE_FRAME_INFO});
+    ASSERT_TRUE(getValueResult.ok());
+    auto propValue = getValueResult.value();
+    ASSERT_EQ(propValue.value.int64Values.size(), static_cast<size_t>(3))
+            << "expect 3 obd2 freeze frames stored";
+
+    std::string propIdStr = StringPrintf("%d", OBD2_FREEZE_FRAME);
+    DumpResult result;
+    for (int64_t timestamp : propValue.value.int64Values) {
+        result = getHardware()->dump(
+                {"--getWithArg", propIdStr, "-i64", StringPrintf("%" PRId64, timestamp)});
+
+        ASSERT_FALSE(result.callerShouldDumpState);
+        ASSERT_NE(result.buffer, "");
+        ASSERT_THAT(result.buffer, ContainsRegex("Get property result:"));
+    }
+
+    // Set the timestamp argument to 0.
+    result = getHardware()->dump({"--getWithArg", propIdStr, "-i64", "0"});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    // There is no freeze obd2 frame at timestamp 0.
+    ASSERT_THAT(result.buffer, ContainsRegex("failed to read property value"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testSaveRestoreProp) {
+    int32_t prop = toInt(VehicleProperty::TIRE_PRESSURE);
+    std::string propIdStr = std::to_string(prop);
+    std::string areaIdStr = std::to_string(WHEEL_FRONT_LEFT);
+
+    DumpResult result = getHardware()->dump({"--save-prop", propIdStr, "-a", areaIdStr});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, ContainsRegex("saved"));
+
+    ASSERT_EQ(setValue(VehiclePropValue{
+                      .prop = prop,
+                      .areaId = WHEEL_FRONT_LEFT,
+                      .value =
+                              {
+                                      .floatValues = {210.0},
+                              },
+              }),
+              StatusCode::OK);
+
+    result = getHardware()->dump({"--restore-prop", propIdStr, "-a", areaIdStr});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, ContainsRegex("restored"));
+
+    auto getResult = getValue(VehiclePropValue{.prop = prop, .areaId = WHEEL_FRONT_LEFT});
+
+    ASSERT_TRUE(getResult.ok());
+    // The default value is 200.0.
+    ASSERT_EQ(getResult.value().value.floatValues, std::vector<float>{200.0});
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpInjectEvent) {
+    int32_t prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+    std::string propIdStr = std::to_string(prop);
+
+    int64_t timestamp = elapsedRealtimeNano();
+    // Inject an event with float value 123.4 and timestamp.
+    DumpResult result = getHardware()->dump(
+            {"--inject-event", propIdStr, "-f", "123.4", "-t", std::to_string(timestamp)});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer,
+                ContainsRegex(StringPrintf("Event for property: %d injected", prop)));
+    ASSERT_TRUE(waitForChangedProperties(prop, 0, /*count=*/1, milliseconds(1000)))
+            << "No changed event received for injected event from vehicle bus";
+    auto events = getChangedProperties();
+    ASSERT_EQ(events.size(), 1u);
+    auto event = events[0];
+    ASSERT_EQ(event.timestamp, timestamp);
+    ASSERT_EQ(event.value.floatValues, std::vector<float>({123.4}));
+}
+
 TEST_F(FakeVehicleHardwareTest, testDumpInvalidOptions) {
     std::vector<std::string> options;
     options.push_back("--invalid");
@@ -1606,6 +1711,260 @@
     ASSERT_EQ(3.402823466E+38f, value.value.floatValues[2]);
 }
 
+struct OptionsTestCase {
+    std::string name;
+    std::vector<std::string> options;
+    std::string expectMsg;
+};
+
+class FakeVehicleHardwareOptionsTest : public FakeVehicleHardwareTest,
+                                       public testing::WithParamInterface<OptionsTestCase> {};
+
+std::vector<OptionsTestCase> GenInvalidOptions() {
+    return {{"unknown_command", {"--unknown"}, "Invalid option: --unknown"},
+            {"help", {"--help"}, "Usage:"},
+            {"genfakedata_no_subcommand",
+             {"--genfakedata"},
+             "No subcommand specified for genfakedata"},
+            {"genfakedata_unknown_subcommand",
+             {"--genfakedata", "--unknown"},
+             "Unknown command: \"--unknown\""},
+            {"genfakedata_start_linear_no_args",
+             {"--genfakedata", "--startlinear"},
+             "incorrect argument count"},
+            {"genfakedata_start_linear_invalid_propId",
+             {"--genfakedata", "--startlinear", "abcd", "0.1", "0.1", "0.1", "0.1", "100000000"},
+             "failed to parse propId as int: \"abcd\""},
+            {"genfakedata_start_linear_invalid_middleValue",
+             {"--genfakedata", "--startlinear", "1", "abcd", "0.1", "0.1", "0.1", "100000000"},
+             "failed to parse middleValue as float: \"abcd\""},
+            {"genfakedata_start_linear_invalid_currentValue",
+             {"--genfakedata", "--startlinear", "1", "0.1", "abcd", "0.1", "0.1", "100000000"},
+             "failed to parse currentValue as float: \"abcd\""},
+            {"genfakedata_start_linear_invalid_dispersion",
+             {"--genfakedata", "--startlinear", "1", "0.1", "0.1", "abcd", "0.1", "100000000"},
+             "failed to parse dispersion as float: \"abcd\""},
+            {"genfakedata_start_linear_invalid_increment",
+             {"--genfakedata", "--startlinear", "1", "0.1", "0.1", "0.1", "abcd", "100000000"},
+             "failed to parse increment as float: \"abcd\""},
+            {"genfakedata_start_linear_invalid_interval",
+             {"--genfakedata", "--startlinear", "1", "0.1", "0.1", "0.1", "0.1", "0.1"},
+             "failed to parse interval as int: \"0.1\""},
+            {"genfakedata_stop_linear_no_args",
+             {"--genfakedata", "--stoplinear"},
+             "incorrect argument count"},
+            {"genfakedata_stop_linear_invalid_propId",
+             {"--genfakedata", "--stoplinear", "abcd"},
+             "failed to parse propId as int: \"abcd\""},
+            {"genfakedata_startjson_no_args",
+             {"--genfakedata", "--startjson"},
+             "incorrect argument count"},
+            {"genfakedata_startjson_invalid_repetition",
+             {"--genfakedata", "--startjson", "--path", "file", "0.1"},
+             "failed to parse repetition as int: \"0.1\""},
+            {"genfakedata_startjson_invalid_json_file",
+             {"--genfakedata", "--startjson", "--path", "file", "1"},
+             "invalid JSON file"},
+            {"genfakedata_stopjson_no_args",
+             {"--genfakedata", "--stopjson"},
+             "incorrect argument count"},
+            {"genfakedata_keypress_no_args",
+             {"--genfakedata", "--keypress"},
+             "incorrect argument count"},
+            {"genfakedata_keypress_invalid_keyCode",
+             {"--genfakedata", "--keypress", "0.1", "1"},
+             "failed to parse keyCode as int: \"0.1\""},
+            {"genfakedata_keypress_invalid_display",
+             {"--genfakedata", "--keypress", "1", "0.1"},
+             "failed to parse display as int: \"0.1\""}};
+}
+
+TEST_P(FakeVehicleHardwareOptionsTest, testInvalidOptions) {
+    auto tc = GetParam();
+
+    DumpResult result = getHardware()->dump(tc.options);
+
+    EXPECT_FALSE(result.callerShouldDumpState);
+    EXPECT_THAT(result.buffer, HasSubstr(tc.expectMsg));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FakeVehicleHardwareOptionsTests, FakeVehicleHardwareOptionsTest,
+        testing::ValuesIn(GenInvalidOptions()),
+        [](const testing::TestParamInfo<FakeVehicleHardwareOptionsTest::ParamType>& info) {
+            return info.param.name;
+        });
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataLinear) {
+    // Start a fake linear data generator for vehicle speed at 0.1s interval.
+    // range: 0 - 100, current value: 30, step: 20.
+    std::string propIdString = StringPrintf("%d", toInt(VehicleProperty::PERF_VEHICLE_SPEED));
+    std::vector<std::string> options = {"--genfakedata",         "--startlinear", propIdString,
+                                        /*middleValue=*/"50",
+                                        /*currentValue=*/"30",
+                                        /*dispersion=*/"50",
+                                        /*increment=*/"20",
+                                        /*interval=*/"100000000"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    ASSERT_TRUE(waitForChangedProperties(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0, /*count=*/5,
+                                         milliseconds(1000)))
+            << "not enough events generated for linear data generator";
+
+    int32_t value = 30;
+    auto events = getChangedProperties();
+    for (size_t i = 0; i < 5; i++) {
+        ASSERT_EQ(1u, events[i].value.floatValues.size());
+        EXPECT_EQ(static_cast<float>(value), events[i].value.floatValues[0]);
+        value = (value + 20) % 100;
+    }
+
+    // Stop the linear generator.
+    options = {"--genfakedata", "--stoplinear", propIdString};
+
+    result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    clearChangedProperties();
+    std::this_thread::sleep_for(std::chrono::milliseconds(200));
+
+    // There should be no new events generated.
+    EXPECT_EQ(0u, getEventCount(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0));
+}
+
+std::string getTestFilePath(const char* filename) {
+    static std::string baseDir = android::base::GetExecutableDirectory();
+    return baseDir + "/" + filename;
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJson) {
+    std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
+                                        getTestFilePath("prop.json"), "2"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    ASSERT_TRUE(waitForChangedProperties(/*count=*/8, milliseconds(1000)))
+            << "not enough events generated for JSON data generator";
+
+    auto events = getChangedProperties();
+    ASSERT_EQ(8u, events.size());
+    // First set of events, we test 1st and the last.
+    EXPECT_EQ(1u, events[0].value.int32Values.size());
+    EXPECT_EQ(8, events[0].value.int32Values[0]);
+    EXPECT_EQ(1u, events[3].value.int32Values.size());
+    EXPECT_EQ(10, events[3].value.int32Values[0]);
+    // Second set of the same events.
+    EXPECT_EQ(1u, events[4].value.int32Values.size());
+    EXPECT_EQ(8, events[4].value.int32Values[0]);
+    EXPECT_EQ(1u, events[7].value.int32Values.size());
+    EXPECT_EQ(10, events[7].value.int32Values[0]);
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonByContent) {
+    std::vector<std::string> options = {
+            "--genfakedata", "--startjson", "--content",
+            "[{\"timestamp\":1000000,\"areaId\":0,\"value\":8,\"prop\":289408000}]", "1"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    ASSERT_TRUE(waitForChangedProperties(/*count=*/1, milliseconds(1000)))
+            << "not enough events generated for JSON data generator";
+
+    auto events = getChangedProperties();
+    ASSERT_EQ(1u, events.size());
+    EXPECT_EQ(1u, events[0].value.int32Values.size());
+    EXPECT_EQ(8, events[0].value.int32Values[0]);
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonInvalidContent) {
+    std::vector<std::string> options = {"--genfakedata", "--startjson", "--content", "[{", "2"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("invalid JSON content"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonInvalidFile) {
+    std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
+                                        getTestFilePath("blahblah.json"), "2"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("invalid JSON file"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonStop) {
+    // No iteration number provided, would loop indefinitely.
+    std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
+                                        getTestFilePath("prop.json")};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    std::string id = result.buffer.substr(result.buffer.find("ID: ") + 4);
+
+    result = getHardware()->dump({"--genfakedata", "--stopjson", id});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonStopInvalidFile) {
+    // No iteration number provided, would loop indefinitely.
+    std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
+                                        getTestFilePath("prop.json")};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    result = getHardware()->dump({"--genfakedata", "--stopjson", "1234"});
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("No JSON event generator found"));
+
+    // TearDown function should destroy the generator which stops the iteration.
+}
+
+TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataKeyPress) {
+    std::vector<std::string> options = {"--genfakedata", "--keypress", "1", "2"};
+
+    DumpResult result = getHardware()->dump(options);
+
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_THAT(result.buffer, HasSubstr("successfully"));
+
+    auto events = getChangedProperties();
+    ASSERT_EQ(2u, events.size());
+    EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[0].prop);
+    EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[1].prop);
+    ASSERT_EQ(3u, events[0].value.int32Values.size());
+    ASSERT_EQ(3u, events[1].value.int32Values.size());
+    EXPECT_EQ(toInt(VehicleHwKeyInputAction::ACTION_DOWN), events[0].value.int32Values[0]);
+    EXPECT_EQ(1, events[0].value.int32Values[1]);
+    EXPECT_EQ(2, events[0].value.int32Values[2]);
+    EXPECT_EQ(toInt(VehicleHwKeyInputAction::ACTION_UP), events[1].value.int32Values[0]);
+    EXPECT_EQ(1, events[1].value.int32Values[1]);
+    EXPECT_EQ(2, events[1].value.int32Values[2]);
+}
+
 TEST_F(FakeVehicleHardwareTest, testGetEchoReverseBytes) {
     ASSERT_EQ(setValue(VehiclePropValue{
                       .prop = ECHO_REVERSE_BYTES,
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/prop.json b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/prop.json
new file mode 100644
index 0000000..7123a00
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/prop.json
@@ -0,0 +1,26 @@
+[
+    {
+        "timestamp": 1000000,
+        "areaId": 0,
+        "value": 8,
+        "prop": 289408000
+    },
+    {
+        "timestamp": 2000000,
+        "areaId": 0,
+        "value": 4,
+        "prop": 289408000
+    },
+    {
+        "timestamp": 3000000,
+        "areaId": 0,
+        "value": 16,
+        "prop": 289408000
+    },
+    {
+        "timestamp": 4000000,
+        "areaId": 0,
+        "value": 10,
+        "prop": 289408000
+    }
+]
diff --git a/automotive/vehicle/aidl/impl/utils/README.md b/automotive/vehicle/aidl/impl/utils/README.md
new file mode 100644
index 0000000..87bb7e3
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/utils/README.md
@@ -0,0 +1,62 @@
+# Utility classes for VHAL implementation
+---
+
+This directory stores utility classes for VHAL implementation. Vendor
+implementation could use utility classes from `common` folder in their
+VHAL implementation.
+
+## common
+
+Defines common utility libraries.
+
+### ConcurrentQueue
+
+Provides a thread-safe concurrent queue object. Useful for adding object to
+a queue in one thread (usually binder thread) and handle the objects in a
+separate handler thread.
+
+### ParcelableUtils
+
+Provides functions to convert between a regular parcelable and a
+`LargeParcelabe`.
+
+A `LargeParcelable` is a parcelable that marshals the payload
+into a shared memory file if the payload is too large to pass across binder.
+It is used to pass large data across binder. Before sending the data, VHAL
+impl should convert a regular parcelabe to a `LargeParcelable`. After receving
+data, VHAL impl should convert a `LargeParcelable` back to regular parcelabe.
+
+### PendingRequestPool
+
+Defines A class for managing pending requests and automatically call timeout
+callback if the request timed-out.
+
+### PropertyUtils
+
+Defines some useful constants.
+
+### RecurrentTimer
+
+Defines a thread-safe recurrent timer that can call a function periodically.
+
+### VehicleHalTypes
+
+Provides a header file that includes many commonly used header files. Useful
+when you are using multiple types defined in VHAL interface.
+
+### VehicleObjectPool
+
+Defines a reusable in-memory pool for `VehiclePropValue`.
+
+### VehiclePropertyStore
+
+Defines an in-memory map for storing vehicle properties. Allows easier insert,
+delete and lookup.
+
+### VehicleUtils
+
+Defines many useful utility functions.
+
+## test
+
+Defines utility libraries for test only.
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/PendingRequestPool.h b/automotive/vehicle/aidl/impl/utils/common/include/PendingRequestPool.h
index 3f8db93..28cf08e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/PendingRequestPool.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/PendingRequestPool.h
@@ -21,7 +21,6 @@
 #include <android-base/result.h>
 #include <android-base/thread_annotations.h>
 
-#include <atomic>
 #include <list>
 #include <mutex>
 #include <thread>
@@ -85,7 +84,7 @@
     std::unordered_map<const void*, std::list<PendingRequest>> mPendingRequestsByClient
             GUARDED_BY(mLock);
     std::thread mThread;
-    std::atomic<bool> mThreadStop = false;
+    bool mThreadStop = false;
     std::condition_variable mCv;
     std::mutex mCvLock;
 
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
index a7fcdcf..6af1223 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
@@ -26,6 +26,7 @@
 #include <aidl/android/hardware/automotive/vehicle/GetValueRequest.h>
 #include <aidl/android/hardware/automotive/vehicle/GetValueResult.h>
 #include <aidl/android/hardware/automotive/vehicle/GetValueResults.h>
+#include <aidl/android/hardware/automotive/vehicle/GsrComplianceRequirementType.h>
 #include <aidl/android/hardware/automotive/vehicle/Obd2CommonIgnitionMonitors.h>
 #include <aidl/android/hardware/automotive/vehicle/Obd2FuelSystemStatus.h>
 #include <aidl/android/hardware/automotive/vehicle/Obd2FuelType.h>
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/PendingRequestPool.cpp b/automotive/vehicle/aidl/impl/utils/common/src/PendingRequestPool.cpp
index 0196edd..ab50499 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/PendingRequestPool.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/PendingRequestPool.cpp
@@ -39,20 +39,27 @@
 
 }  // namespace
 
-PendingRequestPool::PendingRequestPool(int64_t timeoutInNano)
-    : mTimeoutInNano(timeoutInNano), mThread([this] {
-          // [this] must be alive within this thread because destructor would wait for this thread
-          // to exit.
-          int64_t sleepTime = std::min(mTimeoutInNano, static_cast<int64_t>(CHECK_TIME_IN_NANO));
-          std::unique_lock<std::mutex> lk(mCvLock);
-          while (!mCv.wait_for(lk, std::chrono::nanoseconds(sleepTime),
-                               [this] { return mThreadStop.load(); })) {
-              checkTimeout();
-          }
-      }) {}
+PendingRequestPool::PendingRequestPool(int64_t timeoutInNano) : mTimeoutInNano(timeoutInNano) {
+    mThread = std::thread([this] {
+        // [this] must be alive within this thread because destructor would wait for this thread
+        // to exit.
+        int64_t sleepTime = std::min(mTimeoutInNano, static_cast<int64_t>(CHECK_TIME_IN_NANO));
+        std::unique_lock<std::mutex> lk(mCvLock);
+        while (!mCv.wait_for(lk, std::chrono::nanoseconds(sleepTime),
+                             [this] { return mThreadStop; })) {
+            checkTimeout();
+        }
+    });
+}
 
 PendingRequestPool::~PendingRequestPool() {
-    mThreadStop = true;
+    {
+        // Even if the shared variable is atomic, it must be modified under the
+        // mutex in order to correctly publish the modification to the waiting
+        // thread.
+        std::unique_lock<std::mutex> lk(mCvLock);
+        mThreadStop = true;
+    }
     mCv.notify_all();
     if (mThread.joinable()) {
         mThread.join();
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp b/automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp
index 8521c4d..fbc79fa 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp
@@ -29,7 +29,9 @@
 
 using ::android::base::ScopedLockAssertion;
 
-RecurrentTimer::RecurrentTimer() : mThread(&RecurrentTimer::loop, this) {}
+RecurrentTimer::RecurrentTimer() {
+    mThread = std::thread(&RecurrentTimer::loop, this);
+}
 
 RecurrentTimer::~RecurrentTimer() {
     {
diff --git a/automotive/vehicle/aidl/impl/vhal/Android.bp b/automotive/vehicle/aidl/impl/vhal/Android.bp
index 5abcaf6..8b4f559 100644
--- a/automotive/vehicle/aidl/impl/vhal/Android.bp
+++ b/automotive/vehicle/aidl/impl/vhal/Android.bp
@@ -66,3 +66,36 @@
         "libbinder_ndk",
     ],
 }
+
+cc_fuzz {
+    name: "android.hardware.automotive.vehicle@V1-default-service_fuzzer",
+    vendor: true,
+    defaults: [
+        "FakeVehicleHardwareDefaults",
+        "VehicleHalDefaults",
+        "android-automotive-large-parcelable-defaults",
+    ],
+    header_libs: [
+        "IVehicleHardware",
+        "VehicleHalDefaultConfig",
+    ],
+    static_libs: [
+        "DefaultVehicleHal",
+        "FakeVehicleHardware",
+        "VehicleHalUtils",
+        "libbase",
+        "libbinder_random_parcel",
+        "libcutils",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder",
+        "libutils",
+    ],
+    srcs: ["src/fuzzer.cpp"],
+    fuzz_config: {
+        cc: [
+            "keithmok@google.com",
+        ],
+    },
+}
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index b191aef..138aad5 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -355,6 +355,9 @@
         ALOGE("getValues: failed to parse getValues requests");
         return std::move(deserializedResults.error());
     }
+    if (callback == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
     const std::vector<GetValueRequest>& getValueRequests =
             deserializedResults.value().getObject()->payloads;
 
@@ -438,6 +441,9 @@
         ALOGE("setValues: failed to parse setValues requests");
         return std::move(deserializedResults.error());
     }
+    if (callback == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
     const std::vector<SetValueRequest>& setValueRequests =
             deserializedResults.value().getObject()->payloads;
 
@@ -629,7 +635,9 @@
         ALOGE("subscribe: invalid subscribe options: %s", getErrorMsg(result).c_str());
         return toScopedAStatus(result);
     }
-
+    if (callback == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
     std::vector<SubscribeOptions> onChangeSubscriptions;
     std::vector<SubscribeOptions> continuousSubscriptions;
     for (const auto& option : options) {
@@ -685,6 +693,9 @@
 
 ScopedAStatus DefaultVehicleHal::unsubscribe(const CallbackType& callback,
                                              const std::vector<int32_t>& propIds) {
+    if (callback == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
     return toScopedAStatus(mSubscriptionManager->unsubscribe(callback->asBinder().get(), propIds));
 }
 
diff --git a/automotive/vehicle/aidl/impl/vhal/src/fuzzer.cpp b/automotive/vehicle/aidl/impl/vhal/src/fuzzer.cpp
new file mode 100644
index 0000000..ac1e3b1
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/vhal/src/fuzzer.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 <DefaultVehicleHal.h>
+#include <FakeVehicleHardware.h>
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+using ::android::fuzzService;
+using ::android::hardware::automotive::vehicle::DefaultVehicleHal;
+using ::android::hardware::automotive::vehicle::fake::FakeVehicleHardware;
+using ::ndk::SharedRefBase;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    std::unique_ptr<FakeVehicleHardware> hardware = std::make_unique<FakeVehicleHardware>();
+    std::shared_ptr<DefaultVehicleHal> vhal =
+            ::ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+
+    fuzzService(vhal->asBinder().get(), FuzzedDataProvider(data, size));
+
+    return 0;
+}
diff --git a/biometrics/common/thread/Android.bp b/biometrics/common/thread/Android.bp
new file mode 100644
index 0000000..a497d01
--- /dev/null
+++ b/biometrics/common/thread/Android.bp
@@ -0,0 +1,26 @@
+cc_library {
+    // 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
+    name: "android.hardware.biometrics.common.thread",
+    export_include_dirs: ["include"],
+    vendor: true,
+    srcs: [
+        "WorkerThread.cpp",
+    ],
+}
+
+cc_test_host {
+    name: "android.hardware.biometrics.common.WorkerThreadTest",
+    local_include_dirs: ["include"],
+    srcs: [
+        "tests/WorkerThreadTest.cpp",
+        "WorkerThread.cpp",
+    ],
+    shared_libs: [
+        "libcutils",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/biometrics/fingerprint/aidl/default/WorkerThread.cpp b/biometrics/common/thread/WorkerThread.cpp
similarity index 89%
rename from biometrics/fingerprint/aidl/default/WorkerThread.cpp
rename to biometrics/common/thread/WorkerThread.cpp
index d1a63d0..61d1a13 100644
--- a/biometrics/fingerprint/aidl/default/WorkerThread.cpp
+++ b/biometrics/common/thread/WorkerThread.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include "WorkerThread.h"
+#include "thread/WorkerThread.h"
 
-namespace aidl::android::hardware::biometrics::fingerprint {
+namespace aidl::android::hardware::biometrics {
 
 // It's important that mThread is initialized after everything else because it runs a member
 // function that may use any member of this class.
@@ -31,7 +31,10 @@
 WorkerThread::~WorkerThread() {
     // This is a signal for threadFunc to terminate as soon as possible, and a hint for schedule
     // that it doesn't need to do any work.
-    mIsDestructing = true;
+    {
+        std::unique_lock<std::mutex> lock(mQueueMutex);
+        mIsDestructing = true;
+    }
     mQueueCond.notify_all();
     mThread.join();
 }
@@ -65,4 +68,4 @@
     }
 }
 
-}  // namespace aidl::android::hardware::biometrics::fingerprint
+}  // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/fingerprint/aidl/default/include/Callable.h b/biometrics/common/thread/include/thread/Callable.h
similarity index 92%
rename from biometrics/fingerprint/aidl/default/include/Callable.h
rename to biometrics/common/thread/include/thread/Callable.h
index c629511..6eeff76 100644
--- a/biometrics/fingerprint/aidl/default/include/Callable.h
+++ b/biometrics/common/thread/include/thread/Callable.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-namespace aidl::android::hardware::biometrics::fingerprint {
+namespace aidl::android::hardware::biometrics {
 
 // Interface for representing parameterless functions. Unlike std::function<void()>, this can also
 // represent move-only lambdas.
@@ -51,4 +51,4 @@
     return std::make_unique<AnyFuncWrapper<T>>(std::move(func));
 }
 
-}  // namespace aidl::android::hardware::biometrics::fingerprint
\ No newline at end of file
+}  // namespace aidl::android::hardware::biometrics
\ No newline at end of file
diff --git a/biometrics/fingerprint/aidl/default/include/WorkerThread.h b/biometrics/common/thread/include/thread/WorkerThread.h
similarity index 95%
rename from biometrics/fingerprint/aidl/default/include/WorkerThread.h
rename to biometrics/common/thread/include/thread/WorkerThread.h
index 6fff4f2..5f89a7f 100644
--- a/biometrics/fingerprint/aidl/default/include/WorkerThread.h
+++ b/biometrics/common/thread/include/thread/WorkerThread.h
@@ -23,7 +23,7 @@
 
 #include "Callable.h"
 
-namespace aidl::android::hardware::biometrics::fingerprint {
+namespace aidl::android::hardware::biometrics {
 
 // A class that encapsulates a worker thread and a task queue, and provides a convenient interface
 // for a Session to schedule its tasks for asynchronous execution.
@@ -76,4 +76,4 @@
     std::thread mThread;
 };
 
-}  // namespace aidl::android::hardware::biometrics::fingerprint
+}  // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp b/biometrics/common/thread/tests/WorkerThreadTest.cpp
similarity index 96%
rename from biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp
rename to biometrics/common/thread/tests/WorkerThreadTest.cpp
index 902fb40..5bb9e7e 100644
--- a/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp
+++ b/biometrics/common/thread/tests/WorkerThreadTest.cpp
@@ -21,12 +21,11 @@
 
 #include <gtest/gtest.h>
 
-#include "WorkerThread.h"
+#include "thread/WorkerThread.h"
 
 namespace {
 
-using aidl::android::hardware::biometrics::fingerprint::Callable;
-using aidl::android::hardware::biometrics::fingerprint::WorkerThread;
+using namespace aidl::android::hardware::biometrics;
 using namespace std::chrono_literals;
 
 TEST(WorkerThreadTest, ScheduleReturnsTrueWhenQueueHasSpace) {
diff --git a/biometrics/common/util/Android.bp b/biometrics/common/util/Android.bp
new file mode 100644
index 0000000..918ef72
--- /dev/null
+++ b/biometrics/common/util/Android.bp
@@ -0,0 +1,18 @@
+cc_library {
+    // 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
+    name: "android.hardware.biometrics.common.util",
+    export_include_dirs: ["include"],
+    vendor: true,
+    srcs: [
+        "CancellationSignal.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+    ],
+}
diff --git a/biometrics/fingerprint/aidl/default/CancellationSignal.cpp b/biometrics/common/util/CancellationSignal.cpp
similarity index 87%
rename from biometrics/fingerprint/aidl/default/CancellationSignal.cpp
rename to biometrics/common/util/CancellationSignal.cpp
index 6598316..7888838 100644
--- a/biometrics/fingerprint/aidl/default/CancellationSignal.cpp
+++ b/biometrics/common/util/CancellationSignal.cpp
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "CancellationSignal.h"
+#include "util/CancellationSignal.h"
 
 #include <android-base/logging.h>
 #include <chrono>
 
-namespace aidl::android::hardware::biometrics::fingerprint {
+namespace aidl::android::hardware::biometrics {
 
 CancellationSignal::CancellationSignal(std::promise<void>&& cancellationPromise)
     : mCancellationPromise(std::move(cancellationPromise)) {}
@@ -34,4 +34,4 @@
     return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
 }
 
-}  // namespace aidl::android::hardware::biometrics::fingerprint
+}  // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/fingerprint/aidl/default/include/CancellationSignal.h b/biometrics/common/util/include/util/CancellationSignal.h
similarity index 83%
rename from biometrics/fingerprint/aidl/default/include/CancellationSignal.h
rename to biometrics/common/util/include/util/CancellationSignal.h
index 99f2fba..be77e29 100644
--- a/biometrics/fingerprint/aidl/default/include/CancellationSignal.h
+++ b/biometrics/common/util/include/util/CancellationSignal.h
@@ -17,13 +17,10 @@
 #pragma once
 
 #include <aidl/android/hardware/biometrics/common/BnCancellationSignal.h>
-#include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
 #include <functional>
 #include <future>
 
-#include "WorkerThread.h"
-
-namespace aidl::android::hardware::biometrics::fingerprint {
+namespace aidl::android::hardware::biometrics {
 
 class CancellationSignal : public common::BnCancellationSignal {
   public:
@@ -39,4 +36,4 @@
 // to this future should be cancelled.
 bool shouldCancel(const std::future<void>& cancellationFuture);
 
-}  // namespace aidl::android::hardware::biometrics::fingerprint
+}  // namespace aidl::android::hardware::biometrics
diff --git a/biometrics/common/util/include/util/Util.h b/biometrics/common/util/include/util/Util.h
new file mode 100644
index 0000000..29ec0f8
--- /dev/null
+++ b/biometrics/common/util/include/util/Util.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+
+#include <chrono>
+#include <regex>
+#include <thread>
+#include <vector>
+
+namespace aidl::android::hardware::biometrics {
+
+#define SLEEP_MS(x) \
+    if (x > 0) std::this_thread::sleep_for(std::chrono::milliseconds(x))
+#define BEGIN_OP(x)            \
+    do {                       \
+        LOG(INFO) << __func__; \
+        SLEEP_MS(x);           \
+    } while (0)
+#define IS_TRUE(x) ((x == "1") || (x == "true"))
+
+// This is for non-test situations, such as casual cuttlefish users, that don't
+// set an explicit value.
+// Some operations (i.e. enroll, authenticate) will be executed in tight loops
+// by parts of the UI or fail if there is no latency. For example, the
+// Face settings page constantly runs auth and the enrollment UI uses a
+// cancel/restart cycle that requires some latency while the activities change.
+#define DEFAULT_LATENCY 800
+
+class Util {
+  public:
+    static int64_t getSystemNanoTime() {
+        timespec now;
+        clock_gettime(CLOCK_MONOTONIC, &now);
+        return now.tv_sec * 1000000000LL + now.tv_nsec;
+    }
+
+    static bool hasElapsed(int64_t start, int64_t durationMillis) {
+        auto now = getSystemNanoTime();
+        if (now < start) return true;
+        if (durationMillis <= 0) return true;
+        return ((now - start) / 1000000LL) > durationMillis;
+    }
+
+    static std::vector<std::string> split(const std::string& str, const std::string& sep) {
+        std::regex regex(sep);
+        std::vector<std::string> parts(
+                std::sregex_token_iterator(str.begin(), str.end(), regex, -1),
+                std::sregex_token_iterator());
+        return parts;
+    }
+};
+
+}  // namespace aidl::android::hardware::biometrics
\ No newline at end of file
diff --git a/biometrics/face/aidl/default/Android.bp b/biometrics/face/aidl/default/Android.bp
index 7f66eca..48c929b 100644
--- a/biometrics/face/aidl/default/Android.bp
+++ b/biometrics/face/aidl/default/Android.bp
@@ -18,10 +18,43 @@
         "libbinder_ndk",
         "android.hardware.biometrics.face-V2-ndk",
         "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.biometrics.common.thread",
+        "android.hardware.biometrics.common.util",
     ],
     srcs: [
         "main.cpp",
         "Face.cpp",
+        "FakeFaceEngine.cpp",
         "Session.cpp",
     ],
+    static_libs: ["android.hardware.biometrics.face.VirtualProps"],
+}
+
+sysprop_library {
+    name: "android.hardware.biometrics.face.VirtualProps",
+    srcs: ["face.sysprop"],
+    property_owner: "Vendor",
+    vendor: true,
+}
+
+cc_test {
+    name: "android.hardware.biometrics.face.FakeFaceEngineTest",
+    srcs: [
+        "tests/FakeFaceEngineTest.cpp",
+        "FakeFaceEngine.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "android.hardware.biometrics.face.VirtualProps",
+        "android.hardware.biometrics.face-V2-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.keymaster-V3-ndk",
+        "android.hardware.biometrics.common.util",
+    ],
+    vendor: true,
+    test_suites: ["general-tests"],
+    require_root: true,
 }
diff --git a/biometrics/face/aidl/default/Face.cpp b/biometrics/face/aidl/default/Face.cpp
index aca3e13..652a7e1 100644
--- a/biometrics/face/aidl/default/Face.cpp
+++ b/biometrics/face/aidl/default/Face.cpp
@@ -17,12 +17,14 @@
 #include "Face.h"
 #include "Session.h"
 
+#include "FakeFaceEngine.h"
+
 namespace aidl::android::hardware::biometrics::face {
 
 const int kSensorId = 4;
-const common::SensorStrength kSensorStrength = common::SensorStrength::STRONG;
+const common::SensorStrength kSensorStrength = FakeFaceEngine::GetSensorStrength();
 const int kMaxEnrollmentsPerUser = 5;
-const FaceSensorType kSensorType = FaceSensorType::RGB;
+const FaceSensorType kSensorType = FakeFaceEngine::GetSensorType();
 const bool kHalControlsPreview = true;
 const std::string kHwComponentId = "faceSensor";
 const std::string kHardwareVersion = "vendor/model/revision";
@@ -69,7 +71,7 @@
 ndk::ScopedAStatus Face::createSession(int32_t /*sensorId*/, int32_t /*userId*/,
                                        const std::shared_ptr<ISessionCallback>& cb,
                                        std::shared_ptr<ISession>* return_val) {
-    *return_val = SharedRefBase::make<Session>(cb);
+    *return_val = SharedRefBase::make<Session>(std::make_unique<FakeFaceEngine>(), cb);
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/biometrics/face/aidl/default/FakeFaceEngine.cpp b/biometrics/face/aidl/default/FakeFaceEngine.cpp
new file mode 100644
index 0000000..0f088f4
--- /dev/null
+++ b/biometrics/face/aidl/default/FakeFaceEngine.cpp
@@ -0,0 +1,318 @@
+#include "FakeFaceEngine.h"
+
+#include <android-base/logging.h>
+
+#include <face.sysprop.h>
+
+#include "util/CancellationSignal.h"
+#include "util/Util.h"
+
+using namespace ::android::face::virt;
+
+namespace aidl::android::hardware::biometrics::face {
+
+FaceSensorType FakeFaceEngine::GetSensorType() {
+    std::string type = FaceHalProperties::type().value_or("");
+    if (type == "IR") {
+        return FaceSensorType::IR;
+    } else {
+        FaceHalProperties::type("RGB");
+        return FaceSensorType::RGB;
+    }
+}
+
+common::SensorStrength FakeFaceEngine::GetSensorStrength() {
+    std::string strength = FaceHalProperties::strength().value_or("");
+    if (strength == "convenience") {
+        return common::SensorStrength::CONVENIENCE;
+    } else if (strength == "weak") {
+        return common::SensorStrength::WEAK;
+    } else {
+        FaceHalProperties::strength("strong");
+        return common::SensorStrength::STRONG;
+    }
+}
+
+void FakeFaceEngine::generateChallengeImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    std::uniform_int_distribution<int64_t> dist;
+    auto challenge = dist(mRandom);
+    FaceHalProperties::challenge(challenge);
+    cb->onChallengeGenerated(challenge);
+}
+
+void FakeFaceEngine::revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
+    BEGIN_OP(0);
+    FaceHalProperties::challenge({});
+    cb->onChallengeRevoked(challenge);
+}
+void FakeFaceEngine::getEnrollmentConfigImpl(ISessionCallback* /*cb*/,
+                                             std::vector<EnrollmentStageConfig>* /*return_val*/) {}
+void FakeFaceEngine::enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                                EnrollmentType /*enrollmentType*/,
+                                const std::vector<Feature>& /*features*/,
+                                const std::future<void>& cancel) {
+    BEGIN_OP(FaceHalProperties::operation_start_enroll_latency().value_or(0));
+    // format is "<id>,<bucket_id>:<delay>:<succeeds>,<bucket_id>:<delay>:<succeeds>...
+    auto nextEnroll = FaceHalProperties::next_enrollment().value_or("");
+    // Erase the next enrollment
+    FaceHalProperties::next_enrollment({});
+
+    AuthenticationFrame frame;
+    frame.data.acquiredInfo = AcquiredInfo::START;
+    frame.data.vendorCode = 0;
+    cb->onAuthenticationFrame(frame);
+
+    // Do proper HAT verification in the real implementation.
+    if (hat.mac.empty()) {
+        LOG(ERROR) << "Fail: hat";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    if (FaceHalProperties::operation_enroll_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_enroll_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    auto parts = Util::split(nextEnroll, ",");
+    if (parts.size() < 2) {
+        LOG(ERROR) << "Fail: invalid next_enrollment for : " << nextEnroll;
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    auto enrollmentId = std::stoi(parts[0]);
+    const int numBuckets = parts.size() - 1;
+    for (size_t i = 1; i < parts.size(); i++) {
+        auto enrollHit = Util::split(parts[i], ":");
+        if (enrollHit.size() != 3) {
+            LOG(ERROR) << "Error when unpacking enrollment hit: " << parts[i];
+            cb->onError(Error::VENDOR, 0 /* vendorError */);
+        }
+        std::string bucket = enrollHit[0];
+        std::string delay = enrollHit[1];
+        std::string succeeds = enrollHit[2];
+
+        SLEEP_MS(std::stoi(delay));
+
+        if (shouldCancel(cancel)) {
+            LOG(ERROR) << "Fail: cancel";
+            cb->onError(Error::CANCELED, 0 /* vendorCode */);
+            return;
+        }
+
+        if (!IS_TRUE(succeeds)) {  // end and failed
+            LOG(ERROR) << "Fail: requested by caller: " << parts[i];
+            cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+            return;
+        }
+
+        EnrollmentFrame frame;
+
+        frame.data.acquiredInfo = AcquiredInfo::GOOD;
+        frame.data.vendorCode = 0;
+        cb->onEnrollmentFrame(frame);
+
+        frame.data.acquiredInfo = AcquiredInfo::VENDOR;
+        frame.data.vendorCode = std::stoi(bucket);
+        cb->onEnrollmentFrame(frame);
+
+        int remainingBuckets = numBuckets - i;
+        if (remainingBuckets > 0) {
+            cb->onEnrollmentProgress(enrollmentId, remainingBuckets);
+        }
+    }
+
+    auto enrollments = FaceHalProperties::enrollments();
+    enrollments.push_back(enrollmentId);
+    FaceHalProperties::enrollments(enrollments);
+    LOG(INFO) << "enrolled : " << enrollmentId;
+    cb->onEnrollmentProgress(enrollmentId, 0);
+}
+
+void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationId*/,
+                                      const std::future<void>& cancel) {
+    BEGIN_OP(FaceHalProperties::operation_authenticate_latency().value_or(0));
+
+    // Signal to the framework that we have begun authenticating.
+    AuthenticationFrame frame;
+    frame.data.acquiredInfo = AcquiredInfo::START;
+    frame.data.vendorCode = 0;
+    cb->onAuthenticationFrame(frame);
+
+    // Also signal that we have opened the camera.
+    frame = {};
+    frame.data.acquiredInfo = AcquiredInfo::FIRST_FRAME_RECEIVED;
+    frame.data.vendorCode = 0;
+    cb->onAuthenticationFrame(frame);
+
+    auto now = Util::getSystemNanoTime();
+    int64_t duration = FaceHalProperties::operation_authenticate_duration().value_or(0);
+    if (duration > 0) {
+        do {
+            SLEEP_MS(5);
+        } while (!Util::hasElapsed(now, duration));
+    }
+
+    if (FaceHalProperties::operation_authenticate_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_authenticate_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    if (FaceHalProperties::lockout().value_or(false)) {
+        LOG(ERROR) << "Fail: lockout";
+        cb->onLockoutPermanent();
+        cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */);
+        return;
+    }
+
+    if (shouldCancel(cancel)) {
+        LOG(ERROR) << "Fail: cancel";
+        cb->onError(Error::CANCELED, 0 /* vendorCode */);
+        return;
+    }
+
+    auto id = FaceHalProperties::enrollment_hit().value_or(0);
+    auto enrolls = FaceHalProperties::enrollments();
+    auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+    if (id < 0 || !isEnrolled) {
+        LOG(ERROR) << (isEnrolled ? "invalid enrollment hit" : "Fail: not enrolled");
+        cb->onAuthenticationFailed();
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    cb->onAuthenticationSucceeded(id, {} /* hat */);
+}
+
+void FakeFaceEngine::detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel) {
+    BEGIN_OP(FaceHalProperties::operation_detect_interaction_latency().value_or(0));
+
+    if (FaceHalProperties::operation_detect_interaction_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_detect_interaction_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    if (shouldCancel(cancel)) {
+        LOG(ERROR) << "Fail: cancel";
+        cb->onError(Error::CANCELED, 0 /* vendorCode */);
+        return;
+    }
+
+    auto id = FaceHalProperties::enrollment_hit().value_or(0);
+    auto enrolls = FaceHalProperties::enrollments();
+    auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+    if (id <= 0 || !isEnrolled) {
+        LOG(ERROR) << "Fail: not enrolled";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    cb->onInteractionDetected();
+}
+
+void FakeFaceEngine::enumerateEnrollmentsImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    std::vector<int32_t> enrollments;
+    for (const auto& enrollmentId : FaceHalProperties::enrollments()) {
+        if (enrollmentId) {
+            enrollments.push_back(*enrollmentId);
+        }
+    }
+    cb->onEnrollmentsEnumerated(enrollments);
+}
+
+void FakeFaceEngine::removeEnrollmentsImpl(ISessionCallback* cb,
+                                           const std::vector<int32_t>& enrollmentIds) {
+    BEGIN_OP(0);
+
+    std::vector<std::optional<int32_t>> newEnrollments;
+    for (const auto& enrollment : FaceHalProperties::enrollments()) {
+        auto id = enrollment.value_or(0);
+        if (std::find(enrollmentIds.begin(), enrollmentIds.end(), id) == enrollmentIds.end()) {
+            newEnrollments.emplace_back(id);
+        }
+    }
+    FaceHalProperties::enrollments(newEnrollments);
+    cb->onEnrollmentsRemoved(enrollmentIds);
+}
+
+void FakeFaceEngine::getFeaturesImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+
+    if (FaceHalProperties::enrollments().empty()) {
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+        return;
+    }
+
+    std::vector<Feature> featuresToReturn = {};
+    for (const auto& feature : FaceHalProperties::features()) {
+        if (feature) {
+            featuresToReturn.push_back((Feature)(*feature));
+        }
+    }
+    cb->onFeaturesRetrieved(featuresToReturn);
+}
+
+void FakeFaceEngine::setFeatureImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                                    Feature feature, bool enabled) {
+    BEGIN_OP(0);
+
+    if (FaceHalProperties::enrollments().empty()) {
+        LOG(ERROR) << "Unable to set feature, enrollments are empty";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+        return;
+    }
+
+    if (hat.mac.empty()) {
+        LOG(ERROR) << "Unable to set feature, invalid hat";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+        return;
+    }
+
+    auto features = FaceHalProperties::features();
+
+    auto itr = std::find_if(features.begin(), features.end(), [feature](const auto& theFeature) {
+        return *theFeature == (int)feature;
+    });
+
+    if (!enabled && (itr != features.end())) {
+        features.erase(itr);
+    } else if (enabled && (itr == features.end())) {
+        features.push_back((int)feature);
+    }
+
+    FaceHalProperties::features(features);
+    cb->onFeatureSet(feature);
+}
+
+void FakeFaceEngine::getAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    // If this is a weak HAL return 0 per the spec.
+    if (GetSensorStrength() != common::SensorStrength::STRONG) {
+        cb->onAuthenticatorIdRetrieved(0);
+    } else {
+        cb->onAuthenticatorIdRetrieved(FaceHalProperties::authenticator_id().value_or(0));
+    }
+}
+
+void FakeFaceEngine::invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    int64_t authenticatorId = FaceHalProperties::authenticator_id().value_or(0);
+    int64_t newId = authenticatorId + 1;
+    FaceHalProperties::authenticator_id(newId);
+    cb->onAuthenticatorIdInvalidated(newId);
+}
+
+void FakeFaceEngine::resetLockoutImpl(ISessionCallback* cb,
+                                      const keymaster::HardwareAuthToken& /*hat*/) {
+    BEGIN_OP(0);
+    FaceHalProperties::lockout(false);
+    cb->onLockoutCleared();
+}
+
+}  // namespace aidl::android::hardware::biometrics::face
\ No newline at end of file
diff --git a/biometrics/face/aidl/default/FakeFaceEngine.h b/biometrics/face/aidl/default/FakeFaceEngine.h
new file mode 100644
index 0000000..edb54ce
--- /dev/null
+++ b/biometrics/face/aidl/default/FakeFaceEngine.h
@@ -0,0 +1,65 @@
+/*
+ * 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/biometrics/common/SensorStrength.h>
+#include <aidl/android/hardware/biometrics/face/BnSession.h>
+#include <aidl/android/hardware/biometrics/face/FaceSensorType.h>
+#include <aidl/android/hardware/biometrics/face/ISessionCallback.h>
+
+#include <random>
+
+#include <future>
+#include <vector>
+
+namespace aidl::android::hardware::biometrics::face {
+
+namespace face = aidl::android::hardware::biometrics::face;
+namespace common = aidl::android::hardware::biometrics::common;
+namespace keymaster = aidl::android::hardware::keymaster;
+
+using aidl::android::hardware::common::NativeHandle;
+// A fake engine that is backed by system properties instead of hardware.
+class FakeFaceEngine {
+  public:
+    FakeFaceEngine() : mRandom(std::mt19937::default_seed) {}
+
+    static face::FaceSensorType GetSensorType();
+    static common::SensorStrength GetSensorStrength();
+    void generateChallengeImpl(ISessionCallback* cb);
+    void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge);
+    void getEnrollmentConfigImpl(ISessionCallback* cb,
+                                 std::vector<EnrollmentStageConfig>* return_val);
+    void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                    EnrollmentType enrollmentType, const std::vector<Feature>& features,
+                    const std::future<void>& cancel);
+    void authenticateImpl(ISessionCallback* cb, int64_t operationId,
+                          const std::future<void>& cancel);
+    void detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel);
+    void enumerateEnrollmentsImpl(ISessionCallback* cb);
+    void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds);
+    void getFeaturesImpl(ISessionCallback* cb);
+    void setFeatureImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                        Feature feature, bool enabled);
+    void getAuthenticatorIdImpl(ISessionCallback* cb);
+    void invalidateAuthenticatorIdImpl(ISessionCallback* cb);
+    void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/);
+
+    std::mt19937 mRandom;
+};
+
+}  // namespace aidl::android::hardware::biometrics::face
\ No newline at end of file
diff --git a/biometrics/face/aidl/default/README.md b/biometrics/face/aidl/default/README.md
new file mode 100644
index 0000000..1655973
--- /dev/null
+++ b/biometrics/face/aidl/default/README.md
@@ -0,0 +1,77 @@
+# Virtual Face HAL
+
+This is a virtual HAL implementation that is backed by system properties
+instead of actual hardware. It's intended for testing and UI development
+on debuggable builds to allow devices to masquerade as alternative device
+types and for emulators.
+
+## Device Selection
+
+You can either run the FakeFaceEngine on a [real device](#actual-device) or a [virtual device/cuttlefish](#getting-started-on-a-virtual-device-cuttlefish). This document should
+help you to get started on either one.
+
+After setting up a device, go ahead and try out [enrolling](#enrolling) & [authenticating](#authenticating)
+
+### Getting started on a Virtual Device (cuttlefish)
+
+
+Note, I'm running this via a cloudtop virtual device.
+
+1. Setup cuttlefish on cloudtop, See [this](https://g3doc.corp.google.com/company/teams/android/teampages/acloud/getting_started.md?cl=head) for more details.
+2. acloud create --local-image
+3. Enter in the shell command to disable hidl
+
+```shell
+$ adb root
+$ adb shell settings put secure com.android.server.biometrics.AuthService.hidlDisabled 1
+$ adb reboot
+```
+4. You should now be able to do fake enrollments and authentications (as seen down below)
+
+### Actual Device
+
+1. Modify your real devices make file (I.E. vendor/google/products/{YOUR_DEVICE}.mk)
+2. Ensure that there is no other face HAL that is being included by the device
+3. Add the following
+```
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.biometrics.face.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/permissions/android.hardware.biometrics.face.xml
+
+PRODUCT_PACKAGES += \
+    android.hardware.biometrics.face-service.example \
+
+```
+4. Now build and flash m -j120 && flash
+5. Run the following commands
+
+```shell
+# This is a temporary workaround
+$ adb root
+$ adb shell setprop persist.vendor.face.virtual.type RGB
+$ adb shell setprop persist.vendor.face.virtual.strength strong
+$ adb shell locksettings set-pin 0000
+$ adb reboot
+```
+
+## Enrolling
+
+```shell
+# authenticar_id,bucket_id:duration:(true|false)....
+$ adb shell setprop vendor.face.virtual.next_enrollment 1,0:500:true,5:250:true,10:150:true,15:500:true
+$ adb shell am start -n com.android.settings/.biometrics.face.FaceEnrollIntroduction
+# If you would like to get rid of the enrollment, run the follwoing command
+$ adb shell setprop persist.vendor.face.virtual.enrollments \"\"
+```
+
+## Authenticating
+
+```shell
+# If enrollment hasn't been setup
+$ adb shell setprop persist.vendor.face.virtual.enrollments 1
+$ adb shell cmd face sync
+# After enrollment has been setup
+$ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800
+$ adb shell setprop vendor.face.virtual.enrollment_hit 1
+# Power button press to simulate auth
+$ adb shell input keyevent 26
+```
diff --git a/biometrics/face/aidl/default/Session.cpp b/biometrics/face/aidl/default/Session.cpp
index 984a1a9..1188459 100644
--- a/biometrics/face/aidl/default/Session.cpp
+++ b/biometrics/face/aidl/default/Session.cpp
@@ -14,139 +14,135 @@
  * limitations under the License.
  */
 
-#include <aidl/android/hardware/biometrics/common/BnCancellationSignal.h>
 #include <android-base/logging.h>
 
 #include "Session.h"
 
 namespace aidl::android::hardware::biometrics::face {
 
-class CancellationSignal : public common::BnCancellationSignal {
-  private:
-    std::shared_ptr<ISessionCallback> cb_;
+constexpr size_t MAX_WORKER_QUEUE_SIZE = 5;
 
-  public:
-    explicit CancellationSignal(std::shared_ptr<ISessionCallback> cb) : cb_(std::move(cb)) {}
-
-    ndk::ScopedAStatus cancel() override {
-        cb_->onError(Error::CANCELED, 0 /* vendorCode */);
-        return ndk::ScopedAStatus::ok();
-    }
-};
-
-Session::Session(std::shared_ptr<ISessionCallback> cb)
-    : cb_(std::move(cb)), mRandom(std::mt19937::default_seed) {}
+Session::Session(std::unique_ptr<FakeFaceEngine> engine, std::shared_ptr<ISessionCallback> cb)
+    : mEngine(std::move(engine)), mCb(std::move(cb)), mRandom(std::mt19937::default_seed) {
+    mThread = std::make_unique<WorkerThread>(MAX_WORKER_QUEUE_SIZE);
+}
 
 ndk::ScopedAStatus Session::generateChallenge() {
     LOG(INFO) << "generateChallenge";
-    if (cb_) {
-        std::uniform_int_distribution<int64_t> dist;
-        auto challenge = dist(mRandom);
-        cb_->onChallengeGenerated(challenge);
-    }
+    mThread->schedule(Callable::from([this] { mEngine->generateChallengeImpl(mCb.get()); }));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::revokeChallenge(int64_t challenge) {
     LOG(INFO) << "revokeChallenge";
-    if (cb_) {
-        cb_->onChallengeRevoked(challenge);
-    }
+    mThread->schedule(Callable::from(
+            [this, challenge] { mEngine->revokeChallengeImpl(mCb.get(), challenge); }));
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Session::getEnrollmentConfig(EnrollmentType /*enrollmentType*/,
-                                                std::vector<EnrollmentStageConfig>* return_val) {
-    *return_val = {};
+ndk::ScopedAStatus Session::getEnrollmentConfig(
+        EnrollmentType /*enrollmentType*/, std::vector<EnrollmentStageConfig>* cancellationSignal) {
+    *cancellationSignal = {};
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::enroll(
-        const keymaster::HardwareAuthToken& /*hat*/, EnrollmentType /*enrollmentType*/,
-        const std::vector<Feature>& /*features*/,
-        const std::optional<NativeHandle>& /*previewSurface*/,
-        std::shared_ptr<biometrics::common::ICancellationSignal>* /*return_val*/) {
+        const keymaster::HardwareAuthToken& hat, EnrollmentType enrollmentType,
+        const std::vector<Feature>& features, const std::optional<NativeHandle>& /*previewSurface*/,
+        std::shared_ptr<biometrics::common::ICancellationSignal>* cancellationSignal) {
     LOG(INFO) << "enroll";
-    if (cb_) {
-        cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
-    }
+    std::promise<void> cancellationPromise;
+    auto cancFuture = cancellationPromise.get_future();
+
+    mThread->schedule(Callable::from(
+            [this, hat, enrollmentType, features, cancFuture = std::move(cancFuture)] {
+                mEngine->enrollImpl(mCb.get(), hat, enrollmentType, features, cancFuture);
+            }));
+
+    *cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Session::authenticate(int64_t /*keystoreOperationId*/,
-                                         std::shared_ptr<common::ICancellationSignal>* return_val) {
+ndk::ScopedAStatus Session::authenticate(
+        int64_t keystoreOperationId,
+        std::shared_ptr<common::ICancellationSignal>* cancellationSignal) {
     LOG(INFO) << "authenticate";
-    if (cb_) {
-        cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
-    }
-    *return_val = SharedRefBase::make<CancellationSignal>(cb_);
+    std::promise<void> cancellationPromise;
+    auto cancFuture = cancellationPromise.get_future();
+
+    mThread->schedule(
+            Callable::from([this, keystoreOperationId, cancFuture = std::move(cancFuture)] {
+                mEngine->authenticateImpl(mCb.get(), keystoreOperationId, cancFuture);
+            }));
+
+    *cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::detectInteraction(
-        std::shared_ptr<common::ICancellationSignal>* /*return_val*/) {
+        std::shared_ptr<common::ICancellationSignal>* cancellationSignal) {
     LOG(INFO) << "detectInteraction";
+    std::promise<void> cancellationPromise;
+    auto cancFuture = cancellationPromise.get_future();
+
+    mThread->schedule(Callable::from([this, cancFuture = std::move(cancFuture)] {
+        mEngine->detectInteractionImpl(mCb.get(), cancFuture);
+    }));
+
+    *cancellationSignal = SharedRefBase::make<CancellationSignal>(std::move(cancellationPromise));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::enumerateEnrollments() {
     LOG(INFO) << "enumerateEnrollments";
-    if (cb_) {
-        cb_->onEnrollmentsEnumerated(std::vector<int32_t>());
-    }
+    mThread->schedule(Callable::from([this] { mEngine->enumerateEnrollmentsImpl(mCb.get()); }));
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Session::removeEnrollments(const std::vector<int32_t>& /*enrollmentIds*/) {
+ndk::ScopedAStatus Session::removeEnrollments(const std::vector<int32_t>& enrollmentIds) {
     LOG(INFO) << "removeEnrollments";
-    if (cb_) {
-        cb_->onEnrollmentsRemoved(std::vector<int32_t>());
-    }
+    mThread->schedule(Callable::from(
+            [this, enrollmentIds] { mEngine->removeEnrollmentsImpl(mCb.get(), enrollmentIds); }));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::getFeatures() {
     LOG(INFO) << "getFeatures";
-    if (cb_) {
-        // Must error out with UNABLE_TO_PROCESS when no faces are enrolled.
-        cb_->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
-    }
+    mThread->schedule(Callable::from([this] { mEngine->getFeaturesImpl(mCb.get()); }));
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Session::setFeature(const keymaster::HardwareAuthToken& /*hat*/,
-                                       Feature /*feature*/, bool /*enabled*/) {
+ndk::ScopedAStatus Session::setFeature(const keymaster::HardwareAuthToken& hat, Feature feature,
+                                       bool enabled) {
     LOG(INFO) << "setFeature";
+    mThread->schedule(Callable::from([this, hat, feature, enabled] {
+        mEngine->setFeatureImpl(mCb.get(), hat, feature, enabled);
+    }));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::getAuthenticatorId() {
     LOG(INFO) << "getAuthenticatorId";
-    if (cb_) {
-        cb_->onAuthenticatorIdRetrieved(0 /* authenticatorId */);
-    }
+    mThread->schedule(Callable::from([this] { mEngine->getAuthenticatorIdImpl(mCb.get()); }));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::invalidateAuthenticatorId() {
     LOG(INFO) << "invalidateAuthenticatorId";
-    if (cb_) {
-        cb_->onAuthenticatorIdInvalidated(0);
-    }
+    mThread->schedule(
+            Callable::from([this] { mEngine->invalidateAuthenticatorIdImpl(mCb.get()); }));
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Session::resetLockout(const keymaster::HardwareAuthToken& /*hat*/) {
+ndk::ScopedAStatus Session::resetLockout(const keymaster::HardwareAuthToken& hat) {
     LOG(INFO) << "resetLockout";
-    if (cb_) {
-        cb_->onLockoutCleared();
-    }
+    mThread->schedule(Callable::from([this, hat] { mEngine->resetLockoutImpl(mCb.get(), hat); }));
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus Session::close() {
-    if (cb_) {
-        cb_->onSessionClosed();
+    if (mCb) {
+        mCb->onSessionClosed();
     }
     return ndk::ScopedAStatus::ok();
 }
diff --git a/biometrics/face/aidl/default/Session.h b/biometrics/face/aidl/default/Session.h
index 9db17d2..7ca6a1f 100644
--- a/biometrics/face/aidl/default/Session.h
+++ b/biometrics/face/aidl/default/Session.h
@@ -21,6 +21,10 @@
 #include <aidl/android/hardware/biometrics/face/BnSession.h>
 #include <aidl/android/hardware/biometrics/face/ISessionCallback.h>
 
+#include "FakeFaceEngine.h"
+#include "thread/WorkerThread.h"
+#include "util/CancellationSignal.h"
+
 namespace aidl::android::hardware::biometrics::face {
 
 namespace common = aidl::android::hardware::biometrics::common;
@@ -30,7 +34,7 @@
 
 class Session : public BnSession {
   public:
-    explicit Session(std::shared_ptr<ISessionCallback> cb);
+    explicit Session(std::unique_ptr<FakeFaceEngine> engine, std::shared_ptr<ISessionCallback> cb);
 
     ndk::ScopedAStatus generateChallenge() override;
 
@@ -85,8 +89,11 @@
     ndk::ScopedAStatus onContextChanged(const common::OperationContext& context) override;
 
   private:
-    std::shared_ptr<ISessionCallback> cb_;
+    std::unique_ptr<FakeFaceEngine> mEngine;
+    std::shared_ptr<ISessionCallback> mCb;
     std::mt19937 mRandom;
+    std::unique_ptr<WorkerThread> mThread;
+    std::shared_ptr<CancellationSignal> mCancellationSignal;
 };
 
 }  // namespace aidl::android::hardware::biometrics::face
diff --git a/biometrics/face/aidl/default/api/android.hardware.biometrics.face.VirtualProps-current.txt b/biometrics/face/aidl/default/api/android.hardware.biometrics.face.VirtualProps-current.txt
new file mode 100644
index 0000000..9548920
--- /dev/null
+++ b/biometrics/face/aidl/default/api/android.hardware.biometrics.face.VirtualProps-current.txt
@@ -0,0 +1,98 @@
+props {
+  owner: Vendor
+  module: "android.face.virt.FaceHalProperties"
+  prop {
+    api_name: "authenticator_id"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.authenticator_id"
+  }
+  prop {
+    api_name: "challenge"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.challenge"
+  }
+  prop {
+    api_name: "enrollment_hit"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.enrollment_hit"
+  }
+  prop {
+    api_name: "enrollments"
+    type: IntegerList
+    access: ReadWrite
+    prop_name: "persist.vendor.face.virtual.enrollments"
+  }
+  prop {
+    api_name: "features"
+    type: IntegerList
+    access: ReadWrite
+    prop_name: "persist.vendor.face.virtual.features"
+  }
+  prop {
+    api_name: "lockout"
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.lockout"
+  }
+  prop {
+    api_name: "next_enrollment"
+    type: String
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.next_enrollment"
+  }
+  prop {
+    api_name: "operation_authenticate_duration"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_authenticate_duration"
+  }
+  prop {
+    api_name: "operation_authenticate_fails"
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_authenticate_fails"
+  }
+  prop {
+    api_name: "operation_authenticate_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_authenticate_latency"
+  }
+  prop {
+    api_name: "operation_detect_interaction_fails"
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_detect_interaction_fails"
+  }
+  prop {
+    api_name: "operation_detect_interaction_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_detect_interaction_latency"
+  }
+  prop {
+    api_name: "operation_enroll_fails"
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_enroll_fails"
+  }
+  prop {
+    api_name: "operation_start_enroll_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.face.virtual.operation_start_enroll_latency"
+  }
+  prop {
+    api_name: "strength"
+    type: String
+    access: ReadWrite
+    prop_name: "persist.vendor.face.virtual.strength"
+    enum_values: "convenience|weak|strong"
+  }
+  prop {
+    api_name: "type"
+    type: String
+    access: ReadWrite
+    prop_name: "persist.vendor.face.virtual.type"
+    enum_values: "IR|RGB"
+  }
+}
diff --git a/biometrics/face/aidl/default/face.sysprop b/biometrics/face/aidl/default/face.sysprop
new file mode 100644
index 0000000..6b0f37f
--- /dev/null
+++ b/biometrics/face/aidl/default/face.sysprop
@@ -0,0 +1,159 @@
+# face.sysprop
+# module becomes static class (Java) / namespace (C++) for serving API
+module: "android.face.virt.FaceHalProperties"
+owner: Vendor
+
+# type of face sensor
+prop {
+    prop_name: "persist.vendor.face.virtual.type"
+    type: String
+    scope: Public
+    access: ReadWrite
+    enum_values: "IR|RGB"
+    api_name: "type"
+}
+
+# the strength of the sensor
+prop {
+    prop_name: "persist.vendor.face.virtual.strength"
+    type: String
+    scope: Public
+    access: ReadWrite
+    enum_values: "convenience|weak|strong"
+    api_name: "strength"
+}
+
+# ids of current enrollments
+prop {
+    prop_name: "persist.vendor.face.virtual.enrollments"
+    type: IntegerList
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollments"
+}
+
+# List of features
+prop {
+    prop_name: "persist.vendor.face.virtual.features"
+    type: IntegerList
+    scope: Public
+    access: ReadWrite
+    api_name: "features"
+}
+
+# authenticate and detectInteraction will succeed with this
+# enrollment id, when present, otherwise they will error
+prop {
+    prop_name: "vendor.face.virtual.enrollment_hit"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollment_hit"
+}
+
+# The initial latency for enrollment
+prop {
+    prop_name: "vendor.face.virtual.operation_start_enroll_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_start_enroll_latency"
+}
+
+# the next enrollment in the format:
+# "<id>,<bucket_id>:<delay>:<succeeds>,<bucket_id>..."
+# for example: "0:1,0:100:1,1:200:1" indicating that bucket 0 took
+# 50 milliseconds, bucket 1 took 100 milliseconds, bucket 2 took 200 milliseconds.
+# Note that it is up to the configuration to determine how many buckets are required
+# to complete an enrollment
+prop {
+    prop_name: "vendor.face.virtual.next_enrollment"
+    type: String
+    scope: Public
+    access: ReadWrite
+    api_name: "next_enrollment"
+}
+
+# value for getAuthenticatorId or 0
+prop {
+    prop_name: "vendor.face.virtual.authenticator_id"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "authenticator_id"
+}
+
+# value for generateChallenge
+prop {
+    prop_name: "vendor.face.virtual.challenge"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "challenge"
+}
+
+# if locked out
+prop {
+    prop_name: "vendor.face.virtual.lockout"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "lockout"
+}
+
+# force all authenticate operations to fail
+prop {
+    prop_name: "vendor.face.virtual.operation_authenticate_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_fails"
+}
+
+# force all detectInteraction operations to fail
+prop {
+    prop_name: "vendor.face.virtual.operation_detect_interaction_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_fails"
+}
+
+# force all enroll operations to fail
+prop {
+    prop_name: "vendor.face.virtual.operation_enroll_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_enroll_fails"
+}
+
+# add a latency to authentication operations
+# Note that this latency is the initial authentication latency that occurs before
+# the HAL will send AcquiredInfo::START and AcquiredInfo::FIRST_FRAME_RECEIVED
+prop {
+    prop_name: "vendor.face.virtual.operation_authenticate_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_latency"
+}
+
+# add a latency to detectInteraction operations
+prop {
+    prop_name: "vendor.face.virtual.operation_detect_interaction_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_latency"
+}
+
+# millisecond duration for authenticate operations
+# (waits for changes to enrollment_hit)
+prop {
+    prop_name: "vendor.face.virtual.operation_authenticate_duration"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_duration"
+}
diff --git a/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
new file mode 100644
index 0000000..c8ad6b7
--- /dev/null
+++ b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp
@@ -0,0 +1,383 @@
+/*
+ * 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 <android/binder_process.h>
+#include <face.sysprop.h>
+#include <gtest/gtest.h>
+
+#include <aidl/android/hardware/biometrics/face/BnSessionCallback.h>
+#include <android-base/logging.h>
+
+#include "FakeFaceEngine.h"
+
+using namespace ::android::face::virt;
+using namespace ::aidl::android::hardware::biometrics::face;
+using namespace ::aidl::android::hardware::keymaster;
+
+namespace aidl::android::hardware::biometrics::face {
+
+class TestSessionCallback : public BnSessionCallback {
+  public:
+    ndk::ScopedAStatus onChallengeGenerated(int64_t challenge) override {
+        mLastChallenge = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onChallengeRevoked(int64_t challenge) override {
+        mLastChallengeRevoked = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onError(Error error, int32_t) override {
+        mError = error;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
+        if (remaining == 0) mLastEnrolled = enrollmentId;
+        return ndk::ScopedAStatus::ok();
+    };
+
+    ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t enrollmentId,
+                                                   const HardwareAuthToken&) override {
+        mLastAuthenticated = enrollmentId;
+        mAuthenticateFailed = false;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticationFailed() override {
+        mLastAuthenticated = 0;
+        mAuthenticateFailed = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onInteractionDetected() override {
+        mInteractionDetectedCount++;
+        return ndk::ScopedAStatus::ok();
+    };
+
+    ::ndk::ScopedAStatus onEnrollmentFrame(const EnrollmentFrame& frame) override {
+        mEnrollmentFrames.push_back(frame.data.vendorCode);
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onEnrollmentsEnumerated(
+            const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentsEnumerated = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentsRemoved(const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentRemoved = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        mAuthenticatorIdInvalidated = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticationFrame(const AuthenticationFrame& /*authFrame*/) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onLockoutPermanent() override {
+        mLockoutPermanent = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onLockoutTimed(int64_t /* timeout */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onLockoutCleared() override {
+        mLockoutPermanent = false;
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
+
+    ::ndk::ScopedAStatus onFeaturesRetrieved(const std::vector<Feature>& features) override {
+        mFeatures = features;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onFeatureSet(Feature feature) override {
+        mLastFeatureSet = feature;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    Error mError = Error::UNKNOWN;
+    int64_t mLastChallenge = -1;
+    int64_t mLastChallengeRevoked = -1;
+    int32_t mLastEnrolled = -1;
+    int32_t mLastAuthenticated = -1;
+    int64_t mLastAuthenticatorId = -1;
+    std::vector<int32_t> mLastEnrollmentsEnumerated;
+    std::vector<int32_t> mLastEnrollmentRemoved;
+    std::vector<Feature> mFeatures;
+    Feature mLastFeatureSet;
+    std::vector<int32_t> mEnrollmentFrames;
+    bool mAuthenticateFailed = false;
+    bool mAuthenticatorIdInvalidated = false;
+    bool mLockoutPermanent = false;
+    int mInteractionDetectedCount = 0;
+};
+
+class FakeFaceEngineTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        LOG(ERROR) << "JRM SETUP";
+        mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
+        FaceHalProperties::enrollments({});
+        FaceHalProperties::challenge({});
+        FaceHalProperties::features({});
+        FaceHalProperties::authenticator_id({});
+        FaceHalProperties::strength("");
+    }
+
+    FakeFaceEngine mEngine;
+    std::shared_ptr<TestSessionCallback> mCallback;
+    std::promise<void> mCancel;
+};
+
+TEST_F(FakeFaceEngineTest, one_eq_one) {
+    ASSERT_EQ(1, 1);
+}
+
+TEST_F(FakeFaceEngineTest, GenerateChallenge) {
+    mEngine.generateChallengeImpl(mCallback.get());
+    ASSERT_EQ(FaceHalProperties::challenge().value(), mCallback->mLastChallenge);
+}
+
+TEST_F(FakeFaceEngineTest, RevokeChallenge) {
+    auto challenge = FaceHalProperties::challenge().value_or(10);
+    mEngine.revokeChallengeImpl(mCallback.get(), challenge);
+    ASSERT_FALSE(FaceHalProperties::challenge().has_value());
+    ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
+}
+
+TEST_F(FakeFaceEngineTest, ResetLockout) {
+    FaceHalProperties::lockout(true);
+    mEngine.resetLockoutImpl(mCallback.get(), {});
+    ASSERT_FALSE(mCallback->mLockoutPermanent);
+    ASSERT_FALSE(FaceHalProperties::lockout().value_or(true));
+}
+
+TEST_F(FakeFaceEngineTest, AuthenticatorId) {
+    FaceHalProperties::authenticator_id(50);
+    mEngine.getAuthenticatorIdImpl(mCallback.get());
+    ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
+    ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFaceEngineTest, GetAuthenticatorIdWeakReturnsZero) {
+    FaceHalProperties::strength("weak");
+    FaceHalProperties::authenticator_id(500);
+    mEngine.getAuthenticatorIdImpl(mCallback.get());
+    ASSERT_EQ(0, mCallback->mLastAuthenticatorId);
+    ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFaceEngineTest, AuthenticatorIdInvalidate) {
+    FaceHalProperties::authenticator_id(500);
+    mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
+    ASSERT_NE(500, FaceHalProperties::authenticator_id().value());
+    ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFaceEngineTest, Enroll) {
+    FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:true");
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
+                       mCancel.get_future());
+    ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
+    ASSERT_EQ(1, FaceHalProperties::enrollments().size());
+    ASSERT_EQ(1, FaceHalProperties::enrollments()[0].value());
+    ASSERT_EQ(1, mCallback->mLastEnrolled);
+}
+
+TEST_F(FakeFaceEngineTest, EnrollFails) {
+    FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
+                       mCancel.get_future());
+    ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
+    ASSERT_EQ(0, FaceHalProperties::enrollments().size());
+}
+
+TEST_F(FakeFaceEngineTest, EnrollCancel) {
+    FaceHalProperties::next_enrollment("1,0:30:true,1:0:true,2:0:true,3:0:true,4:0:false");
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mCancel.set_value();
+    mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
+                       mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastEnrolled);
+    ASSERT_EQ(0, FaceHalProperties::enrollments().size());
+    ASSERT_FALSE(FaceHalProperties::next_enrollment().has_value());
+}
+
+TEST_F(FakeFaceEngineTest, Authenticate) {
+    FaceHalProperties::enrollments({100});
+    FaceHalProperties::enrollment_hit(100);
+    mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
+
+    ASSERT_EQ(100, mCallback->mLastAuthenticated);
+    ASSERT_FALSE(mCallback->mAuthenticateFailed);
+}
+
+TEST_F(FakeFaceEngineTest, AuthenticateCancel) {
+    FaceHalProperties::enrollments({100});
+    FaceHalProperties::enrollment_hit(100);
+    mCancel.set_value();
+    mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+}
+
+TEST_F(FakeFaceEngineTest, AuthenticateFailedForUnEnrolled) {
+    FaceHalProperties::enrollments({3});
+    FaceHalProperties::enrollment_hit(100);
+    mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
+    ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError);
+    ASSERT_TRUE(mCallback->mAuthenticateFailed);
+}
+
+TEST_F(FakeFaceEngineTest, DetectInteraction) {
+    FaceHalProperties::enrollments({100});
+    FaceHalProperties::enrollment_hit(100);
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFaceEngineTest, DetectInteractionCancel) {
+    FaceHalProperties::enrollments({100});
+    FaceHalProperties::enrollment_hit(100);
+    mCancel.set_value();
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+}
+
+TEST_F(FakeFaceEngineTest, GetFeatureEmpty) {
+    mEngine.getFeaturesImpl(mCallback.get());
+    ASSERT_TRUE(mCallback->mFeatures.empty());
+}
+
+TEST_F(FakeFaceEngineTest, SetFeature) {
+    FaceHalProperties::enrollments({1});
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
+    auto features = mCallback->mFeatures;
+    ASSERT_TRUE(features.empty());
+    ASSERT_EQ(Feature::REQUIRE_ATTENTION, mCallback->mLastFeatureSet);
+
+    mEngine.getFeaturesImpl(mCallback.get());
+    features = mCallback->mFeatures;
+    ASSERT_FALSE(features.empty());
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
+}
+
+TEST_F(FakeFaceEngineTest, ToggleFeature) {
+    FaceHalProperties::enrollments({1});
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
+    mEngine.getFeaturesImpl(mCallback.get());
+    auto features = mCallback->mFeatures;
+    ASSERT_FALSE(features.empty());
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
+
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
+    mEngine.getFeaturesImpl(mCallback.get());
+    features = mCallback->mFeatures;
+    ASSERT_TRUE(features.empty());
+}
+
+TEST_F(FakeFaceEngineTest, TurningOffNonExistentFeatureDoesNothing) {
+    FaceHalProperties::enrollments({1});
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
+    mEngine.getFeaturesImpl(mCallback.get());
+    auto features = mCallback->mFeatures;
+    ASSERT_TRUE(features.empty());
+}
+
+TEST_F(FakeFaceEngineTest, SetMultipleFeatures) {
+    FaceHalProperties::enrollments({1});
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
+    mEngine.getFeaturesImpl(mCallback.get());
+    auto features = mCallback->mFeatures;
+    ASSERT_EQ(3, features.size());
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
+    ASSERT_NE(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
+}
+
+TEST_F(FakeFaceEngineTest, SetMultipleFeaturesAndTurnOffSome) {
+    FaceHalProperties::enrollments({1});
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
+    mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, false);
+    mEngine.getFeaturesImpl(mCallback.get());
+    auto features = mCallback->mFeatures;
+    ASSERT_EQ(2, features.size());
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
+    ASSERT_NE(features.end(),
+              std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
+    ASSERT_EQ(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
+}
+
+TEST_F(FakeFaceEngineTest, Enumerate) {
+    FaceHalProperties::enrollments({120, 3});
+    mEngine.enumerateEnrollmentsImpl(mCallback.get());
+    auto enrolls = mCallback->mLastEnrollmentsEnumerated;
+    ASSERT_FALSE(enrolls.empty());
+    ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
+    ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
+}
+
+TEST_F(FakeFaceEngineTest, RemoveEnrollments) {
+    FaceHalProperties::enrollments({120, 3, 100});
+    mEngine.removeEnrollmentsImpl(mCallback.get(), {120, 100});
+    mEngine.enumerateEnrollmentsImpl(mCallback.get());
+    auto enrolls = mCallback->mLastEnrollmentsEnumerated;
+    ASSERT_FALSE(enrolls.empty());
+    ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
+    ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
+    ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 100));
+}
+
+TEST_F(FakeFaceEngineTest, ResetLockoutWithAuth) {
+    FaceHalProperties::lockout(true);
+    FaceHalProperties::enrollments({33});
+    FaceHalProperties::enrollment_hit(33);
+    auto cancelFuture = mCancel.get_future();
+    mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
+
+    ASSERT_TRUE(mCallback->mLockoutPermanent);
+
+    mEngine.resetLockoutImpl(mCallback.get(), {} /* hat */);
+    ASSERT_FALSE(mCallback->mLockoutPermanent);
+    FaceHalProperties::enrollment_hit(33);
+    mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
+    ASSERT_EQ(33, mCallback->mLastAuthenticated);
+    ASSERT_FALSE(mCallback->mAuthenticateFailed);
+}
+
+}  // namespace aidl::android::hardware::biometrics::face
\ No newline at end of file
diff --git a/biometrics/fingerprint/aidl/OWNERS b/biometrics/fingerprint/aidl/OWNERS
index 36d7261..e162d2d 100644
--- a/biometrics/fingerprint/aidl/OWNERS
+++ b/biometrics/fingerprint/aidl/OWNERS
@@ -1,2 +1,4 @@
 ilyamaty@google.com
-kchyn@google.com
+jeffpu@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 430bf3c..31fd96b 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -11,14 +11,13 @@
     name: "android.hardware.biometrics.fingerprint-service.example",
     vendor: true,
     relative_install_path: "hw",
-    init_rc: ["fingerprint-default.rc"],
-    vintf_fragments: ["fingerprint-default.xml"],
+    init_rc: ["fingerprint-example.rc"],
+    vintf_fragments: ["fingerprint-example.xml"],
     local_include_dirs: ["include"],
     srcs: [
-        "CancellationSignal.cpp",
+        "FakeFingerprintEngine.cpp",
         "Fingerprint.cpp",
         "Session.cpp",
-        "WorkerThread.cpp",
         "main.cpp",
     ],
     shared_libs: [
@@ -26,18 +25,38 @@
         "libbinder_ndk",
         "android.hardware.biometrics.fingerprint-V2-ndk",
         "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.biometrics.common.thread",
+        "android.hardware.biometrics.common.util",
     ],
+    static_libs: ["android.hardware.biometrics.fingerprint.VirtualProps"],
 }
 
-cc_test_host {
-    name: "android.hardware.biometrics.fingerprint.WorkerThreadTest",
+cc_test {
+    name: "android.hardware.biometrics.fingerprint.FakeFingerprintEngineTest",
     local_include_dirs: ["include"],
     srcs: [
-        "tests/WorkerThreadTest.cpp",
-        "WorkerThread.cpp",
+        "tests/FakeFingerprintEngineTest.cpp",
+        "FakeFingerprintEngine.cpp",
     ],
     shared_libs: [
-        "libcutils",
+        "libbase",
+        "libbinder_ndk",
     ],
+    static_libs: [
+        "android.hardware.biometrics.fingerprint.VirtualProps",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.keymaster-V3-ndk",
+        "android.hardware.biometrics.common.util",
+    ],
+    vendor: true,
     test_suites: ["general-tests"],
+    require_root: true,
+}
+
+sysprop_library {
+    name: "android.hardware.biometrics.fingerprint.VirtualProps",
+    srcs: ["fingerprint.sysprop"],
+    property_owner: "Vendor",
+    vendor: true,
 }
diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
new file mode 100644
index 0000000..138caa0
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FakeFingerprintEngine.h"
+
+#include <android-base/logging.h>
+
+#include <fingerprint.sysprop.h>
+
+#include "util/CancellationSignal.h"
+#include "util/Util.h"
+
+using namespace ::android::fingerprint::virt;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+void FakeFingerprintEngine::generateChallengeImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    std::uniform_int_distribution<int64_t> dist;
+    auto challenge = dist(mRandom);
+    FingerprintHalProperties::challenge(challenge);
+    cb->onChallengeGenerated(challenge);
+}
+
+void FakeFingerprintEngine::revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
+    BEGIN_OP(0);
+    FingerprintHalProperties::challenge({});
+    cb->onChallengeRevoked(challenge);
+}
+
+void FakeFingerprintEngine::enrollImpl(ISessionCallback* cb,
+                                       const keymaster::HardwareAuthToken& hat,
+                                       const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_enroll_latency().value_or(DEFAULT_LATENCY));
+
+    // Do proper HAT verification in the real implementation.
+    if (hat.mac.empty()) {
+        LOG(ERROR) << "Fail: hat";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    if (FingerprintHalProperties::operation_enroll_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_enroll_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    // format is "<id>:<progress_ms>,<progress_ms>,...:<result>
+    auto nextEnroll = FingerprintHalProperties::next_enrollment().value_or("");
+    auto parts = Util::split(nextEnroll, ":");
+    if (parts.size() != 3) {
+        LOG(ERROR) << "Fail: invalid next_enrollment";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+    auto enrollmentId = std::stoi(parts[0]);
+    auto progress = Util::split(parts[1], ",");
+    for (size_t i = 0; i < progress.size(); i++) {
+        auto left = progress.size() - i - 1;
+        SLEEP_MS(std::stoi(progress[i]));
+
+        if (shouldCancel(cancel)) {
+            LOG(ERROR) << "Fail: cancel";
+            cb->onError(Error::CANCELED, 0 /* vendorCode */);
+            return;
+        }
+
+        cb->onAcquired(AcquiredInfo::GOOD, 0 /* vendorCode */);
+        if (left == 0 && !IS_TRUE(parts[2])) {  // end and failed
+            LOG(ERROR) << "Fail: requested by caller: " << nextEnroll;
+            FingerprintHalProperties::next_enrollment({});
+            cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+        } else {  // progress and update props if last time
+            if (left == 0) {
+                auto enrollments = FingerprintHalProperties::enrollments();
+                enrollments.emplace_back(enrollmentId);
+                FingerprintHalProperties::enrollments(enrollments);
+                FingerprintHalProperties::next_enrollment({});
+                LOG(INFO) << "Enrolled: " << enrollmentId;
+            }
+            cb->onEnrollmentProgress(enrollmentId, left);
+        }
+    }
+}
+
+void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* operationId */,
+                                             const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_authenticate_latency().value_or(DEFAULT_LATENCY));
+
+    auto now = Util::getSystemNanoTime();
+    int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(0);
+    do {
+        if (FingerprintHalProperties::operation_authenticate_fails().value_or(false)) {
+            LOG(ERROR) << "Fail: operation_authenticate_fails";
+            cb->onError(Error::VENDOR, 0 /* vendorError */);
+            return;
+        }
+
+        if (FingerprintHalProperties::lockout().value_or(false)) {
+            LOG(ERROR) << "Fail: lockout";
+            cb->onLockoutPermanent();
+            cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */);
+            return;
+        }
+
+        if (shouldCancel(cancel)) {
+            LOG(ERROR) << "Fail: cancel";
+            cb->onError(Error::CANCELED, 0 /* vendorCode */);
+            return;
+        }
+
+        auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+        auto enrolls = FingerprintHalProperties::enrollments();
+        auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+        if (id > 0 && isEnrolled) {
+            cb->onAuthenticationSucceeded(id, {} /* hat */);
+            return;
+        }
+
+        SLEEP_MS(100);
+    } while (!Util::hasElapsed(now, duration));
+
+    LOG(ERROR) << "Fail: not enrolled";
+    cb->onAuthenticationFailed();
+    cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+}
+
+void FakeFingerprintEngine::detectInteractionImpl(ISessionCallback* cb,
+                                                  const std::future<void>& cancel) {
+    BEGIN_OP(FingerprintHalProperties::operation_detect_interaction_latency().value_or(
+            DEFAULT_LATENCY));
+
+    if (FingerprintHalProperties::operation_detect_interaction_fails().value_or(false)) {
+        LOG(ERROR) << "Fail: operation_detect_interaction_fails";
+        cb->onError(Error::VENDOR, 0 /* vendorError */);
+        return;
+    }
+
+    if (shouldCancel(cancel)) {
+        LOG(ERROR) << "Fail: cancel";
+        cb->onError(Error::CANCELED, 0 /* vendorCode */);
+        return;
+    }
+
+    auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+    auto enrolls = FingerprintHalProperties::enrollments();
+    auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+    if (id <= 0 || !isEnrolled) {
+        LOG(ERROR) << "Fail: not enrolled";
+        cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+        return;
+    }
+
+    cb->onInteractionDetected();
+}
+
+void FakeFingerprintEngine::enumerateEnrollmentsImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+
+    std::vector<int32_t> ids;
+    for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+        auto id = enrollment.value_or(0);
+        if (id > 0) {
+            ids.push_back(id);
+        }
+    }
+
+    cb->onEnrollmentsEnumerated(ids);
+}
+
+void FakeFingerprintEngine::removeEnrollmentsImpl(ISessionCallback* cb,
+                                                  const std::vector<int32_t>& enrollmentIds) {
+    BEGIN_OP(0);
+
+    std::vector<std::optional<int32_t>> newEnrollments;
+    std::vector<int32_t> removed;
+    for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+        auto id = enrollment.value_or(0);
+        if (std::find(enrollmentIds.begin(), enrollmentIds.end(), id) != enrollmentIds.end()) {
+            removed.push_back(id);
+        } else if (id > 0) {
+            newEnrollments.emplace_back(id);
+        }
+    }
+    FingerprintHalProperties::enrollments(newEnrollments);
+
+    cb->onEnrollmentsRemoved(enrollmentIds);
+}
+
+void FakeFingerprintEngine::getAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    cb->onAuthenticatorIdRetrieved(FingerprintHalProperties::authenticator_id().value_or(0));
+}
+
+void FakeFingerprintEngine::invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
+    BEGIN_OP(0);
+    auto id = FingerprintHalProperties::authenticator_id().value_or(0);
+    auto newId = id + 1;
+    FingerprintHalProperties::authenticator_id(newId);
+    cb->onAuthenticatorIdInvalidated(newId);
+}
+
+void FakeFingerprintEngine::resetLockoutImpl(ISessionCallback* cb,
+                                             const keymaster::HardwareAuthToken& /*hat*/) {
+    BEGIN_OP(0);
+    FingerprintHalProperties::lockout(false);
+    cb->onLockoutCleared();
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index 1f14de6..71dc660 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -16,15 +16,19 @@
 
 #include "Fingerprint.h"
 
+#include <fingerprint.sysprop.h>
 #include "Session.h"
 
+#include <android-base/logging.h>
+
+using namespace ::android::fingerprint::virt;
+
 namespace aidl::android::hardware::biometrics::fingerprint {
 namespace {
 constexpr size_t MAX_WORKER_QUEUE_SIZE = 5;
 constexpr int SENSOR_ID = 1;
 constexpr common::SensorStrength SENSOR_STRENGTH = common::SensorStrength::STRONG;
 constexpr int MAX_ENROLLMENTS_PER_USER = 5;
-constexpr FingerprintSensorType SENSOR_TYPE = FingerprintSensorType::REAR;
 constexpr bool SUPPORTS_NAVIGATION_GESTURES = true;
 constexpr char HW_COMPONENT_ID[] = "fingerprintSensor";
 constexpr char HW_VERSION[] = "vendor/model/revision";
@@ -51,8 +55,18 @@
                                      0 /* sensorLocationY */, 0 /* sensorRadius */,
                                      "" /* display */};
 
+    FingerprintSensorType sensorType = FingerprintSensorType::UNKNOWN;
+    std::string sensorTypeProp = FingerprintHalProperties::type().value_or("");
+    if (sensorTypeProp == "" || sensorTypeProp == "default" || sensorTypeProp == "rear") {
+        sensorType = FingerprintSensorType::REAR;
+    }
+    if (sensorType == FingerprintSensorType::UNKNOWN) {
+        UNIMPLEMENTED(FATAL) << "unrecognized or unimplemented fingerprint behavior: "
+                             << sensorTypeProp;
+    }
+
     *out = {{commonProps,
-             SENSOR_TYPE,
+             sensorType,
              {sensorLocation},
              SUPPORTS_NAVIGATION_GESTURES,
              false /* supportsDetectInteraction */}};
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
new file mode 100644
index 0000000..a6e6b81
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -0,0 +1,88 @@
+# Virtual Fingerprint HAL
+
+This is a virtual HAL implementation that is backed by system properties instead
+of actual hardware. It's intended for testing and UI development on debuggable
+builds to allow devices to masquerade as alternative device types and for
+emulators.
+
+## Supported Devices
+
+This HAL can be used on emulators, like cuttlefish, or on real devices. Add the
+following to your device's `.mk` file to include it:
+
+```
+PRODUCT_PACKAGES_DEBUG += android.hardware.biometrics.fingerprint-service.example
+```
+
+The virtual HAL will be ignored if a real HAL is also installed on the target
+device. Set the `biometric_virtual_enabled` settings and reboot the device to
+switch to the virtual HAL. Unset it and reboot again to switch back.
+
+## Getting Started
+
+First, set the type of sensor the device should use, enable the virtual
+extensions in the framework, and reboot.
+
+This doesn't work with HIDL and you typically need to have a PIN or password set
+for things to work correctly, so this is a good time to set those too.
+
+```shell
+$ adb root
+$ adb shell settings put secure biometric_virtual_enabled 1
+$ adb shell setprop persist.vendor.fingerprint.virtual.type rear
+$ adb shell locksettings set-pin 0000
+$ adb shell settings put secure com.android.server.biometrics.AuthService.hidlDisabled 1
+$ adb reboot
+```
+
+### Enrollments
+
+Next, setup enrollments on the device. This can either be done through the UI,
+or via adb.
+
+#### UI Enrollment
+
+1. Tee up the results of the enrollment before starting the process:
+
+      ```shell
+      $ adb shell setprop vendor.fingerprint.virtual.next_enrollment 1:100,100,100:true
+      ```
+2. Navigate to `Settings -> Security -> Fingerprint Unlock` and follow the
+   prompts.
+3. Verify the enrollments in the UI:
+
+      ```shell
+      $ adb shell getprop persist.vendor.fingerprint.virtual.enrollments
+      ```
+
+#### Direct Enrollment
+
+To set enrollment directly without the UI:
+
+```shell
+$ adb root
+$ adb shell setprop persist.vendor.fingerprint.virtual.enrollments 1
+$ adb shell cmd fingerprint sync
+```
+
+**Note: You may need to do this twice.** The templates are checked as part of
+some lazy operations, like user switching and startup, which can cause the
+framework to delete the enrollments before the sync operation runs. Until this
+is fixed, just run the commands twice as a workaround.
+
+### Authenticate
+
+To authenticate successfully set the enrolled id that should succeed. Unset it
+or change the value to make authenticate operations fail:
+
+````shell
+$ adb shell setprop vendor.fingerprint.virtual.enrollment_hit 1
+````
+
+### View HAL State
+
+To view all the properties of the HAL (see `fingerprint.sysprop` for the API):
+
+```shell
+$ adb shell getprop | grep vendor.fingerprint.virtual
+```
diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp
index 452ed12..ab91e98 100644
--- a/biometrics/fingerprint/aidl/default/Session.cpp
+++ b/biometrics/fingerprint/aidl/default/Session.cpp
@@ -18,7 +18,7 @@
 
 #include <android-base/logging.h>
 
-#include "CancellationSignal.h"
+#include "util/CancellationSignal.h"
 
 namespace aidl::android::hardware::biometrics::fingerprint {
 
@@ -101,7 +101,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->enrollImpl(mCb.get(), hat);
+            mEngine->enrollImpl(mCb.get(), hat, cancFuture);
         }
         enterIdling();
     }));
@@ -123,7 +123,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->authenticateImpl(mCb.get(), operationId);
+            mEngine->authenticateImpl(mCb.get(), operationId, cancFuture);
         }
         enterIdling();
     }));
@@ -144,7 +144,7 @@
         if (shouldCancel(cancFuture)) {
             mCb->onError(Error::CANCELED, 0 /* vendorCode */);
         } else {
-            mEngine->detectInteractionImpl(mCb.get());
+            mEngine->detectInteractionImpl(mCb.get(), cancFuture);
         }
         enterIdling();
     }));
diff --git a/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
new file mode 100644
index 0000000..4724ff4
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
@@ -0,0 +1,85 @@
+props {
+  owner: Vendor
+  module: "android.fingerprint.virt.FingerprintHalProperties"
+  prop {
+    api_name: "authenticator_id"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.authenticator_id"
+  }
+  prop {
+    api_name: "challenge"
+    type: Long
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.challenge"
+  }
+  prop {
+    api_name: "enrollment_hit"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+  }
+  prop {
+    api_name: "enrollments"
+    type: IntegerList
+    access: ReadWrite
+    prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+  }
+  prop {
+    api_name: "lockout"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.lockout"
+  }
+  prop {
+    api_name: "next_enrollment"
+    type: String
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.next_enrollment"
+  }
+  prop {
+    api_name: "operation_authenticate_duration"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+  }
+  prop {
+    api_name: "operation_authenticate_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+  }
+  prop {
+    api_name: "operation_authenticate_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+  }
+  prop {
+    api_name: "operation_detect_interaction_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+  }
+  prop {
+    api_name: "operation_detect_interaction_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+  }
+  prop {
+    api_name: "operation_enroll_fails"
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+  }
+  prop {
+    api_name: "operation_enroll_latency"
+    type: Integer
+    access: ReadWrite
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+  }
+  prop {
+    api_name: "type"
+    type: String
+    access: ReadWrite
+    prop_name: "persist.vendor.fingerprint.virtual.type"
+    enum_values: "default|rear|udfps|side"
+  }
+}
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-default.rc b/biometrics/fingerprint/aidl/default/fingerprint-default.rc
deleted file mode 100644
index eb62c56..0000000
--- a/biometrics/fingerprint/aidl/default/fingerprint-default.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service vendor.fingerprint-default /vendor/bin/hw/android.hardware.biometrics.fingerprint-service.example
-    class hal
-    user nobody
-    group nobody
-
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-example.rc b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
new file mode 100644
index 0000000..574438e
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
@@ -0,0 +1,4 @@
+service vendor.fingerprint-example /vendor/bin/hw/android.hardware.biometrics.fingerprint-service.example
+    class hal
+    user nobody
+    group nobody
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-default.xml b/biometrics/fingerprint/aidl/default/fingerprint-example.xml
similarity index 79%
rename from biometrics/fingerprint/aidl/default/fingerprint-default.xml
rename to biometrics/fingerprint/aidl/default/fingerprint-example.xml
index d322eb6..05d1279 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint-default.xml
+++ b/biometrics/fingerprint/aidl/default/fingerprint-example.xml
@@ -2,6 +2,6 @@
     <hal format="aidl">
         <name>android.hardware.biometrics.fingerprint</name>
         <version>2</version>
-        <fqname>IFingerprint/default</fqname>
+        <fqname>IFingerprint/virtual</fqname>
     </hal>
 </manifest>
diff --git a/biometrics/fingerprint/aidl/default/fingerprint.sysprop b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
new file mode 100644
index 0000000..12c8648
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
@@ -0,0 +1,135 @@
+# fingerprint.sysprop
+# module becomes static class (Java) / namespace (C++) for serving API
+module: "android.fingerprint.virt.FingerprintHalProperties"
+owner: Vendor
+
+# type of fingerprint sensor
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.type"
+    type: String
+    scope: Public
+    access: ReadWrite
+    enum_values: "default|rear|udfps|side"
+    api_name: "type"
+}
+
+# ids of call current enrollments
+prop {
+    prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+    type: IntegerList
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollments"
+}
+
+# authenticate and detectInteraction will succeed with this
+# enrollment id, when present, otherwise they will error
+prop {
+    prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "enrollment_hit"
+}
+
+# the next enrollment in the format: "<id>:<delay>,<delay>,...:<result>"
+# for example: "2:0:true"
+# this property is reset after enroll completes
+prop {
+    prop_name: "vendor.fingerprint.virtual.next_enrollment"
+    type: String
+    scope: Public
+    access: ReadWrite
+    api_name: "next_enrollment"
+}
+
+# value for getAuthenticatorId or 0
+prop {
+    prop_name: "vendor.fingerprint.virtual.authenticator_id"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "authenticator_id"
+}
+
+# value for generateChallenge
+prop {
+    prop_name: "vendor.fingerprint.virtual.challenge"
+    type: Long
+    scope: Public
+    access: ReadWrite
+    api_name: "challenge"
+}
+
+# if locked out
+prop {
+    prop_name: "vendor.fingerprint.virtual.lockout"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "lockout"
+}
+
+# force all authenticate operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_fails"
+}
+
+# force all detectInteraction operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_fails"
+}
+
+# force all enroll operations to fail
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+    type: Boolean
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_enroll_fails"
+}
+
+# add a latency to authentication operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_latency"
+}
+
+# add a latency to detectInteraction operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_detect_interaction_latency"
+}
+
+# add a latency to enroll operations
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_enroll_latency"
+}
+
+# millisecond duration for authenticate operations
+# (waits for changes to enrollment_hit)
+prop {
+    prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+    type: Integer
+    scope: Public
+    access: ReadWrite
+    api_name: "operation_authenticate_duration"
+}
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
index b927770..eb810da 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
@@ -15,72 +15,35 @@
  */
 
 #pragma once
+#include <aidl/android/hardware/biometrics/common/SensorStrength.h>
+#include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
 
-#include <android-base/logging.h>
 #include <random>
 
+#include <future>
+#include <vector>
+
+using namespace ::aidl::android::hardware::biometrics::common;
+
 namespace aidl::android::hardware::biometrics::fingerprint {
 
+// A fake engine that is backed by system properties instead of hardware.
 class FakeFingerprintEngine {
   public:
     FakeFingerprintEngine() : mRandom(std::mt19937::default_seed) {}
 
-    void generateChallengeImpl(ISessionCallback* cb) {
-        LOG(INFO) << "generateChallengeImpl";
-        std::uniform_int_distribution<int64_t> dist;
-        auto challenge = dist(mRandom);
-        cb->onChallengeGenerated(challenge);
-    }
-
-    void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
-        LOG(INFO) << "revokeChallengeImpl";
-        cb->onChallengeRevoked(challenge);
-    }
-
-    void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat) {
-        LOG(INFO) << "enrollImpl";
-        // Do proper HAT verification in the real implementation.
-        if (hat.mac.empty()) {
-            cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
-            return;
-        }
-        cb->onEnrollmentProgress(0 /* enrollmentId */, 0 /* remaining */);
-    }
-
-    void authenticateImpl(ISessionCallback* cb, int64_t /* operationId */) {
-        LOG(INFO) << "authenticateImpl";
-        cb->onAuthenticationSucceeded(0 /* enrollmentId */, {} /* hat */);
-    }
-
-    void detectInteractionImpl(ISessionCallback* cb) {
-        LOG(INFO) << "detectInteractionImpl";
-        cb->onInteractionDetected();
-    }
-
-    void enumerateEnrollmentsImpl(ISessionCallback* cb) {
-        LOG(INFO) << "enumerateEnrollmentsImpl";
-        cb->onEnrollmentsEnumerated({} /* enrollmentIds */);
-    }
-
-    void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds) {
-        LOG(INFO) << "removeEnrollmentsImpl";
-        cb->onEnrollmentsRemoved(enrollmentIds);
-    }
-
-    void getAuthenticatorIdImpl(ISessionCallback* cb) {
-        LOG(INFO) << "getAuthenticatorIdImpl";
-        cb->onAuthenticatorIdRetrieved(0 /* authenticatorId */);
-    }
-
-    void invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
-        LOG(INFO) << "invalidateAuthenticatorIdImpl";
-        cb->onAuthenticatorIdInvalidated(0 /* newAuthenticatorId */);
-    }
-
-    void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) {
-        LOG(INFO) << "resetLockoutImpl";
-        cb->onLockoutCleared();
-    }
+    void generateChallengeImpl(ISessionCallback* cb);
+    void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge);
+    void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+                    const std::future<void>& cancel);
+    void authenticateImpl(ISessionCallback* cb, int64_t operationId,
+                          const std::future<void>& cancel);
+    void detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel);
+    void enumerateEnrollmentsImpl(ISessionCallback* cb);
+    void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds);
+    void getAuthenticatorIdImpl(ISessionCallback* cb);
+    void invalidateAuthenticatorIdImpl(ISessionCallback* cb);
+    void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/);
 
     std::mt19937 mRandom;
 };
diff --git a/biometrics/fingerprint/aidl/default/include/Fingerprint.h b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
index 7bd3d6d..20def0c 100644
--- a/biometrics/fingerprint/aidl/default/include/Fingerprint.h
+++ b/biometrics/fingerprint/aidl/default/include/Fingerprint.h
@@ -20,7 +20,7 @@
 
 #include "FakeFingerprintEngine.h"
 #include "Session.h"
-#include "WorkerThread.h"
+#include "thread/WorkerThread.h"
 
 namespace aidl::android::hardware::biometrics::fingerprint {
 
diff --git a/biometrics/fingerprint/aidl/default/include/Session.h b/biometrics/fingerprint/aidl/default/include/Session.h
index acd5def..104d819 100644
--- a/biometrics/fingerprint/aidl/default/include/Session.h
+++ b/biometrics/fingerprint/aidl/default/include/Session.h
@@ -20,7 +20,7 @@
 #include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
 
 #include "FakeFingerprintEngine.h"
-#include "WorkerThread.h"
+#include "thread/WorkerThread.h"
 
 namespace aidl::android::hardware::biometrics::fingerprint {
 
diff --git a/biometrics/fingerprint/aidl/default/main.cpp b/biometrics/fingerprint/aidl/default/main.cpp
index c985201..0e672b1 100644
--- a/biometrics/fingerprint/aidl/default/main.cpp
+++ b/biometrics/fingerprint/aidl/default/main.cpp
@@ -27,7 +27,7 @@
     ABinderProcess_setThreadPoolMaxThreadCount(0);
     std::shared_ptr<Fingerprint> hal = ndk::SharedRefBase::make<Fingerprint>();
 
-    const std::string instance = std::string(Fingerprint::descriptor) + "/default";
+    const std::string instance = std::string(Fingerprint::descriptor) + "/virtual";
     binder_status_t status = AServiceManager_addService(hal->asBinder().get(), instance.c_str());
     CHECK_EQ(status, STATUS_OK);
 
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
new file mode 100644
index 0000000..742d933
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_process.h>
+#include <fingerprint.sysprop.h>
+#include <gtest/gtest.h>
+
+#include <aidl/android/hardware/biometrics/fingerprint/BnSessionCallback.h>
+
+#include "FakeFingerprintEngine.h"
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+using namespace ::aidl::android::hardware::keymaster;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class TestSessionCallback : public BnSessionCallback {
+  public:
+    ndk::ScopedAStatus onChallengeGenerated(int64_t challenge) override {
+        mLastChallenge = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onChallengeRevoked(int64_t challenge) override {
+        mLastChallengeRevoked = challenge;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onError(fingerprint::Error error, int32_t) override {
+        mError = error;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
+        if (remaining == 0) mLastEnrolled = enrollmentId;
+        return ndk::ScopedAStatus::ok();
+    };
+
+    ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t enrollmentId,
+                                                   const keymaster::HardwareAuthToken&) override {
+        mLastAuthenticated = enrollmentId;
+        mAuthenticateFailed = false;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticationFailed() override {
+        mLastAuthenticated = 0;
+        mAuthenticateFailed = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onInteractionDetected() override {
+        mInteractionDetectedCount++;
+        return ndk::ScopedAStatus::ok();
+    };
+    ndk::ScopedAStatus onAcquired(AcquiredInfo /*info*/, int32_t /*vendorCode*/) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onEnrollmentsEnumerated(
+            const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentEnumerated = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onEnrollmentsRemoved(const std::vector<int32_t>& enrollmentIds) override {
+        mLastEnrollmentRemoved = enrollmentIds;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t authenticatorId) override {
+        mLastAuthenticatorId = authenticatorId;
+        mAuthenticatorIdInvalidated = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ::ndk::ScopedAStatus onLockoutPermanent() override {
+        mLockoutPermanent = true;
+        return ndk::ScopedAStatus::ok();
+    };
+    ndk::ScopedAStatus onLockoutTimed(int64_t /* timeout */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ndk::ScopedAStatus onLockoutCleared() override { return ndk::ScopedAStatus::ok(); }
+    ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
+
+    Error mError = Error::UNKNOWN;
+    int64_t mLastChallenge = -1;
+    int64_t mLastChallengeRevoked = -1;
+    int32_t mLastEnrolled = -1;
+    int32_t mLastAuthenticated = -1;
+    int64_t mLastAuthenticatorId = -1;
+    std::vector<int32_t> mLastEnrollmentEnumerated;
+    std::vector<int32_t> mLastEnrollmentRemoved;
+    bool mAuthenticateFailed = false;
+    bool mAuthenticatorIdInvalidated = false;
+    bool mLockoutPermanent = false;
+    int mInteractionDetectedCount = 0;
+};
+
+class FakeFingerprintEngineTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        FingerprintHalProperties::operation_enroll_latency(0);
+        FingerprintHalProperties::operation_authenticate_latency(0);
+        FingerprintHalProperties::operation_detect_interaction_latency(0);
+        mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
+    }
+
+    FakeFingerprintEngine mEngine;
+    std::shared_ptr<TestSessionCallback> mCallback;
+    std::promise<void> mCancel;
+};
+
+TEST_F(FakeFingerprintEngineTest, GenerateChallenge) {
+    mEngine.generateChallengeImpl(mCallback.get());
+    ASSERT_EQ(FingerprintHalProperties::challenge().value(), mCallback->mLastChallenge);
+}
+
+TEST_F(FakeFingerprintEngineTest, RevokeChallenge) {
+    auto challenge = FingerprintHalProperties::challenge().value_or(10);
+    mEngine.revokeChallengeImpl(mCallback.get(), challenge);
+    ASSERT_FALSE(FingerprintHalProperties::challenge().has_value());
+    ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
+}
+
+TEST_F(FakeFingerprintEngineTest, ResetLockout) {
+    FingerprintHalProperties::lockout(true);
+    mEngine.resetLockoutImpl(mCallback.get(), {});
+    ASSERT_FALSE(FingerprintHalProperties::lockout().value_or(true));
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorId) {
+    FingerprintHalProperties::authenticator_id(50);
+    mEngine.getAuthenticatorIdImpl(mCallback.get());
+    ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
+    ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorIdInvalidate) {
+    FingerprintHalProperties::authenticator_id(500);
+    mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
+    ASSERT_NE(500, FingerprintHalProperties::authenticator_id().value());
+    ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, Enroll) {
+    FingerprintHalProperties::enrollments({});
+    FingerprintHalProperties::next_enrollment("4:0,0:true");
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+    ASSERT_EQ(1, FingerprintHalProperties::enrollments().size());
+    ASSERT_EQ(4, FingerprintHalProperties::enrollments()[0].value());
+    ASSERT_EQ(4, mCallback->mLastEnrolled);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollCancel) {
+    FingerprintHalProperties::enrollments({});
+    auto next = "4:0,0:true";
+    FingerprintHalProperties::next_enrollment(next);
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mCancel.set_value();
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastEnrolled);
+    ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+    ASSERT_EQ(next, FingerprintHalProperties::next_enrollment().value_or(""));
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollFail) {
+    FingerprintHalProperties::enrollments({});
+    auto next = "2:0,0:false";
+    FingerprintHalProperties::next_enrollment(next);
+    keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+    mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+    ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastEnrolled);
+    ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+    ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+}
+
+TEST_F(FakeFingerprintEngineTest, Authenticate) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_FALSE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(2, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateCancel) {
+    FingerprintHalProperties::enrollments({2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mCancel.set_value();
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(-1, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotSet) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit({});
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotEnrolled) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(3);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mAuthenticateFailed);
+    ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateLockout) {
+    FingerprintHalProperties::enrollments({22, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    FingerprintHalProperties::lockout(true);
+    mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+    ASSERT_TRUE(mCallback->mLockoutPermanent);
+    ASSERT_NE(mCallback->mError, Error::UNKNOWN);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetect) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectCancel) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(2);
+    mCancel.set_value();
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(Error::CANCELED, mCallback->mError);
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotSet) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit({});
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotEnrolled) {
+    FingerprintHalProperties::enrollments({1, 2});
+    FingerprintHalProperties::enrollment_hit(25);
+    mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+    ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnumerateEnrolled) {
+    FingerprintHalProperties::enrollments({2, 4, 8});
+    mEngine.enumerateEnrollmentsImpl(mCallback.get());
+    ASSERT_EQ(3, mCallback->mLastEnrollmentEnumerated.size());
+    for (auto id : FingerprintHalProperties::enrollments()) {
+        ASSERT_TRUE(std::find(mCallback->mLastEnrollmentEnumerated.begin(),
+                              mCallback->mLastEnrollmentEnumerated.end(),
+                              id) != mCallback->mLastEnrollmentEnumerated.end());
+    }
+}
+
+TEST_F(FakeFingerprintEngineTest, RemoveEnrolled) {
+    FingerprintHalProperties::enrollments({2, 4, 8, 1});
+    mEngine.removeEnrollmentsImpl(mCallback.get(), {2, 8});
+    auto enrolls = FingerprintHalProperties::enrollments();
+    ASSERT_EQ(2, mCallback->mLastEnrollmentRemoved.size());
+    for (auto id : {2, 8}) {
+        ASSERT_TRUE(std::find(mCallback->mLastEnrollmentRemoved.begin(),
+                              mCallback->mLastEnrollmentRemoved.end(),
+                              id) != mCallback->mLastEnrollmentRemoved.end());
+    }
+    ASSERT_EQ(2, enrolls.size());
+    for (auto id : {1, 4}) {
+        ASSERT_TRUE(std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end());
+    }
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/biometrics/fingerprint/aidl/vts/Android.bp b/biometrics/fingerprint/aidl/vts/Android.bp
index 30d5624..a474f66 100644
--- a/biometrics/fingerprint/aidl/vts/Android.bp
+++ b/biometrics/fingerprint/aidl/vts/Android.bp
@@ -15,8 +15,8 @@
     ],
     srcs: ["VtsHalBiometricsFingerprintTargetTest.cpp"],
     static_libs: [
-        "android.hardware.biometrics.common-V1-ndk",
-        "android.hardware.biometrics.fingerprint-V1-ndk",
+        "android.hardware.biometrics.common-V2-ndk",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
         "android.hardware.keymaster-V3-ndk",
     ],
     shared_libs: [
diff --git a/broadcastradio/common/utils/WorkerThread.cpp b/broadcastradio/common/utils/WorkerThread.cpp
index 31f4d3f..dd87f53 100644
--- a/broadcastradio/common/utils/WorkerThread.cpp
+++ b/broadcastradio/common/utils/WorkerThread.cpp
@@ -31,7 +31,11 @@
     return lhs.when > rhs.when;
 }
 
-WorkerThread::WorkerThread() : mIsTerminating(false), mThread(&WorkerThread::threadLoop, this) {}
+WorkerThread::WorkerThread() : mIsTerminating(false) {
+    // putting mThread in constructor instead of initializer list
+    // to ensure all class members are init before mThread starts
+    mThread = std::thread(&WorkerThread::threadLoop, this);
+}
 
 WorkerThread::~WorkerThread() {
     {
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 943fdb1..386db52 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>
@@ -130,6 +142,7 @@
         <interface>
             <name>IFingerprint</name>
             <instance>default</instance>
+            <instance>virtual</instance>
         </interface>
     </hal>
     <hal format="hidl" optional="true">
diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
index fa294ff..35225d9 100644
--- a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
+++ b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp
@@ -737,6 +737,39 @@
     }
 }
 
+/*
+ * Test that no two display configs are exactly the same.
+ */
+TEST_P(GraphicsComposerHidlTest, GetDisplayConfigNoRepetitions) {
+    for (const auto& display : mDisplays) {
+        std::vector<Config> configs = mComposerClient->getDisplayConfigs(display.get());
+        for (int i = 0; i < configs.size(); i++) {
+            for (int j = i + 1; j < configs.size(); j++) {
+                const int32_t width1 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[i], IComposerClient::Attribute::WIDTH);
+                const int32_t height1 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[i], IComposerClient::Attribute::HEIGHT);
+                const int32_t vsyncPeriod1 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[i], IComposerClient::Attribute::VSYNC_PERIOD);
+                const int32_t group1 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[i], IComposerClient::Attribute::CONFIG_GROUP);
+
+                const int32_t width2 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[j], IComposerClient::Attribute::WIDTH);
+                const int32_t height2 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[j], IComposerClient::Attribute::HEIGHT);
+                const int32_t vsyncPeriod2 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[j], IComposerClient::Attribute::VSYNC_PERIOD);
+                const int32_t group2 = mComposerClient->getDisplayAttribute_2_4(
+                        display.get(), configs[j], IComposerClient::Attribute::CONFIG_GROUP);
+
+                ASSERT_FALSE(width1 == width2 && height1 == height2 &&
+                             vsyncPeriod1 == vsyncPeriod2 && group1 == group2);
+            }
+        }
+    }
+}
+
 }  // namespace
 }  // namespace vts
 }  // namespace V2_4
diff --git a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
index 047109e..93b646f 100644
--- a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
+++ b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
@@ -2122,6 +2122,41 @@
     EXPECT_TRUE(mComposerClient->setPowerMode(getPrimaryDisplayId(), PowerMode::OFF).isOk());
 }
 
+/*
+ * Test that no two display configs are exactly the same.
+ */
+TEST_P(GraphicsComposerAidlTest, GetDisplayConfigNoRepetitions) {
+    for (const auto& display : mDisplays) {
+        const auto& [status, configs] = mComposerClient->getDisplayConfigs(display.getDisplayId());
+        for (std::vector<int>::size_type i = 0; i < configs.size(); i++) {
+            for (std::vector<int>::size_type j = i + 1; j < configs.size(); j++) {
+                const auto& [widthStatus1, width1] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[i], DisplayAttribute::WIDTH);
+                const auto& [heightStatus1, height1] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[i], DisplayAttribute::HEIGHT);
+                const auto& [vsyncPeriodStatus1, vsyncPeriod1] =
+                        mComposerClient->getDisplayAttribute(display.getDisplayId(), configs[i],
+                                                             DisplayAttribute::VSYNC_PERIOD);
+                const auto& [groupStatus1, group1] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[i], DisplayAttribute::CONFIG_GROUP);
+
+                const auto& [widthStatus2, width2] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[j], DisplayAttribute::WIDTH);
+                const auto& [heightStatus2, height2] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[j], DisplayAttribute::HEIGHT);
+                const auto& [vsyncPeriodStatus2, vsyncPeriod2] =
+                        mComposerClient->getDisplayAttribute(display.getDisplayId(), configs[j],
+                                                             DisplayAttribute::VSYNC_PERIOD);
+                const auto& [groupStatus2, group2] = mComposerClient->getDisplayAttribute(
+                        display.getDisplayId(), configs[j], DisplayAttribute::CONFIG_GROUP);
+
+                ASSERT_FALSE(width1 == width2 && height1 == height2 &&
+                             vsyncPeriod1 == vsyncPeriod2 && group1 == group2);
+            }
+        }
+    }
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsComposerAidlCommandTest);
 INSTANTIATE_TEST_SUITE_P(
         PerInstance, GraphicsComposerAidlCommandTest,
diff --git a/radio/aidl/Android.bp b/radio/aidl/Android.bp
index 98eafc1..c0609d8 100644
--- a/radio/aidl/Android.bp
+++ b/radio/aidl/Android.bp
@@ -15,7 +15,7 @@
     stability: "vintf",
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -39,7 +39,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -63,7 +63,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -87,7 +87,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -111,7 +111,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -135,7 +135,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -162,7 +162,7 @@
     ],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
@@ -189,7 +189,7 @@
     imports: ["android.hardware.radio"],
     backend: {
         cpp: {
-            enabled: false,
+            enabled: true,
         },
         java: {
             sdk_version: "module_current",
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyMode.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyMode.aidl
new file mode 100644
index 0000000..071e6b5
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyMode.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.radio.network;
+@Backing(type="int") @JavaDerive(toString=true) @VintfStability
+enum EmergencyMode {
+  EMERGENCY_WWAN = 1,
+  EMERGENCY_WLAN = 2,
+  EMERGENCY_CALLBACK = 3,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyNetworkScanTrigger.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyNetworkScanTrigger.aidl
new file mode 100644
index 0000000..2797aff
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyNetworkScanTrigger.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.radio.network;
+@JavaDerive(toString=true) @VintfStability
+parcelable EmergencyNetworkScanTrigger {
+  android.hardware.radio.AccessNetwork[] accessNetwork;
+  android.hardware.radio.network.EmergencyScanType scanType;
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl
new file mode 100644
index 0000000..cb598f3
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.radio.network;
+@JavaDerive(toString=true) @VintfStability
+parcelable EmergencyRegResult {
+  android.hardware.radio.AccessNetwork accessNetwork;
+  android.hardware.radio.network.RegState regState;
+  android.hardware.radio.network.Domain emcDomain;
+  boolean isEmcBearerSupported;
+  byte nwProvidedEmc;
+  byte nwProvidedEmf;
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyScanType.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyScanType.aidl
new file mode 100644
index 0000000..5e86c76
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyScanType.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.radio.network;
+@Backing(type="int") @JavaDerive(toString=true) @VintfStability
+enum EmergencyScanType {
+  NO_PREFERENCE = 0,
+  LIMITED_SERVICE = 1,
+  FULL_SERVICE = 2,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetwork.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetwork.aidl
index 2b70e45..832738f 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetwork.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetwork.aidl
@@ -70,4 +70,8 @@
   oneway void supplyNetworkDepersonalization(in int serial, in String netPin);
   oneway void setUsageSetting(in int serial, in android.hardware.radio.network.UsageSetting usageSetting);
   oneway void getUsageSetting(in int serial);
+  oneway void setEmergencyMode(int serial, in android.hardware.radio.network.EmergencyMode emcModeType);
+  oneway void triggerEmergencyNetworkScan(int serial, in android.hardware.radio.network.EmergencyNetworkScanTrigger request);
+  oneway void cancelEmergencyNetworkScan(in int serial);
+  oneway void exitEmergencyMode(in int serial);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkIndication.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkIndication.aidl
index bd03c51..0f017ea 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkIndication.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkIndication.aidl
@@ -48,4 +48,5 @@
   oneway void restrictedStateChanged(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.network.PhoneRestrictedState state);
   oneway void suppSvcNotify(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.network.SuppSvcNotification suppSvc);
   oneway void voiceRadioTechChanged(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.RadioTechnology rat);
+  oneway void emergencyNetworkScanResult(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.network.EmergencyRegResult result);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkResponse.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkResponse.aidl
index 5f6c736..24d587e 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkResponse.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/IRadioNetworkResponse.aidl
@@ -69,4 +69,8 @@
   oneway void supplyNetworkDepersonalizationResponse(in android.hardware.radio.RadioResponseInfo info, in int remainingRetries);
   oneway void setUsageSettingResponse(in android.hardware.radio.RadioResponseInfo info);
   oneway void getUsageSettingResponse(in android.hardware.radio.RadioResponseInfo info, in android.hardware.radio.network.UsageSetting usageSetting);
+  oneway void setEmergencyModeResponse(in android.hardware.radio.RadioResponseInfo info, in android.hardware.radio.network.EmergencyRegResult regState);
+  oneway void triggerEmergencyNetworkScanResponse(in android.hardware.radio.RadioResponseInfo info);
+  oneway void exitEmergencyModeResponse(in android.hardware.radio.RadioResponseInfo info);
+  oneway void cancelEmergencyNetworkScanResponse(in android.hardware.radio.RadioResponseInfo info);
 }
diff --git a/radio/aidl/android/hardware/radio/messaging/GsmSmsMessage.aidl b/radio/aidl/android/hardware/radio/messaging/GsmSmsMessage.aidl
index ee62d95..b256c9a 100644
--- a/radio/aidl/android/hardware/radio/messaging/GsmSmsMessage.aidl
+++ b/radio/aidl/android/hardware/radio/messaging/GsmSmsMessage.aidl
@@ -27,6 +27,7 @@
     /**
      * SMS in PDU format as an ASCII hex string less the SMSC address.
      * TP-Layer-Length is be "strlen(pdu)/2
+     * TP - MessageRef field of pdu must not be modified by modem
      */
     String pdu;
 }
diff --git a/radio/aidl/android/hardware/radio/network/EmergencyMode.aidl b/radio/aidl/android/hardware/radio/network/EmergencyMode.aidl
new file mode 100644
index 0000000..25031a9
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/network/EmergencyMode.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.radio.network;
+
+@VintfStability
+@Backing(type="int")
+@JavaDerive(toString=true)
+enum EmergencyMode {
+    /**
+     * Mode Type Emergency WWAN, indicates that the current domain selected for the Emergency call
+     * is cellular.
+     */
+    EMERGENCY_WWAN = 1,
+
+    /**
+     * Mode Type Emergency WLAN, indicates that the current domain selected for the Emergency call
+     * is WLAN/WIFI.
+     */
+    EMERGENCY_WLAN = 2,
+
+    /**
+     * Mode Type Emergency Callback, indicates that the current mode set request is for Emergency
+     * callback.
+     */
+    EMERGENCY_CALLBACK = 3,
+}
diff --git a/radio/aidl/android/hardware/radio/network/EmergencyNetworkScanTrigger.aidl b/radio/aidl/android/hardware/radio/network/EmergencyNetworkScanTrigger.aidl
new file mode 100644
index 0000000..0a22e4c
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/network/EmergencyNetworkScanTrigger.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.radio.network;
+import android.hardware.radio.AccessNetwork;
+import android.hardware.radio.network.EmergencyScanType;
+
+@VintfStability
+@JavaDerive(toString=true)
+parcelable EmergencyNetworkScanTrigger{
+    /**
+     * Access network to be prioritized during emergency scan. The 1st entry has the highest
+     * priority.
+     */
+    AccessNetwork[] accessNetwork;
+
+    /**
+     * Scan type indicates the type of scans to be performed i.e. limited scan, full service scan or
+     * any scan.
+     */
+    EmergencyScanType scanType;
+}
diff --git a/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl b/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl
new file mode 100644
index 0000000..cf5caa4
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl
@@ -0,0 +1,57 @@
+/*
+ * 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.radio.network;
+import android.hardware.radio.AccessNetwork;
+import android.hardware.radio.network.RegState;
+import android.hardware.radio.network.Domain;
+
+@VintfStability
+@JavaDerive(toString=true)
+parcelable EmergencyRegResult {
+    /**
+     * Indicates the cellular access network of the current emergency capable system.
+     */
+    AccessNetwork accessNetwork;
+
+    /**
+     * Registration state of the current emergency capable system.
+     */
+    RegState regState;
+
+    /**
+     * EMC domain indicates the current domain of the acquired system.
+     */
+    Domain emcDomain;
+
+    /**
+     * This indicates if camped network support VoLTE emergency bearers.
+     * This should only be set if the UE is in LTE mode.
+     */
+    boolean isEmcBearerSupported;
+
+    /**
+     * The value of the network provided EMC 5G Registration ACCEPT.
+     * This should be set only if  the UE is in 5G mode.
+     */
+    byte nwProvidedEmc;
+
+    /**
+     * The value of the network provided EMF ( EPS Fallback) in 5G Registration ACCEPT.
+     * This should not be set if UE is not in 5G mode.
+     */
+    byte nwProvidedEmf;
+}
diff --git a/radio/aidl/android/hardware/radio/network/EmergencyScanType.aidl b/radio/aidl/android/hardware/radio/network/EmergencyScanType.aidl
new file mode 100644
index 0000000..72c5490
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/network/EmergencyScanType.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.radio.network;
+
+@VintfStability
+@Backing(type="int")
+@JavaDerive(toString=true)
+enum EmergencyScanType {
+    /**
+     * Scan Type No Preference, indicates that the modem can scan for emergency
+     * service as per modem’s implementation.
+     */
+    NO_PREFERENCE = 0,
+
+    /**
+     * Scan Type limited, indicates that the modem will scan for
+     * emergency service in limited service mode.
+     */
+    LIMITED_SERVICE = 1,
+
+    /**
+     * Scan Type Full Service, indicates that the modem will scan for
+     * emergency service in Full service mode.
+     */
+    FULL_SERVICE = 2,
+}
diff --git a/radio/aidl/android/hardware/radio/network/IRadioNetwork.aidl b/radio/aidl/android/hardware/radio/network/IRadioNetwork.aidl
index cce52ff..0ac8b0e 100644
--- a/radio/aidl/android/hardware/radio/network/IRadioNetwork.aidl
+++ b/radio/aidl/android/hardware/radio/network/IRadioNetwork.aidl
@@ -27,6 +27,8 @@
 import android.hardware.radio.network.RadioBandMode;
 import android.hardware.radio.network.SignalThresholdInfo;
 import android.hardware.radio.network.UsageSetting;
+import android.hardware.radio.network.EmergencyNetworkScanTrigger;
+import android.hardware.radio.network.EmergencyMode;
 
 /**
  * This interface is used by telephony and telecom to talk to cellular radio for network APIs.
@@ -437,4 +439,44 @@
      * @param serial Serial number of request.
      */
     oneway void getUsageSetting(in int serial);
+
+    /**
+     * Set the Emergency Mode
+     *
+     * @param serial Serial number of the request.
+     * @param emcModeType Defines the radio emergency mode type/radio network required/
+     * type of service to be scanned.
+     *
+     * Response function is IRadioEmergencyResponse.setEmergencyModeResponse()
+     */
+    void setEmergencyMode(int serial, in EmergencyMode emcModeType );
+
+    /**
+     * Triggers an Emergency network scan.
+     *
+     * @param serial Serial number of the request.
+     * @param request Defines the radio target networks/preferred network/
+     * Max Scan Time and type of service to be scanned.
+     *
+     * Response function is IRadioEmergencyResponse.triggerEmergencyNetworkScanResponse()
+     */
+    void triggerEmergencyNetworkScan( int serial, in EmergencyNetworkScanTrigger request);
+
+    /**
+     * Cancels ongoing Emergency network scan
+     *
+     * @param serial Serial number of the request.
+     *
+     * Response function is IRadioEmergencyResponse.cancelEmergencyNetworkScan()
+     */
+    void cancelEmergencyNetworkScan(in int serial);
+
+    /**
+     * Exits ongoing Emergency Mode
+     *
+     * @param serial Serial number of the request.
+     *
+     * Response function is IRadioEmergencyResponse.exitEmergencyModeResponse()
+     */
+    void exitEmergencyMode(in int serial);
 }
diff --git a/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl b/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
index f471433..47d932d 100644
--- a/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
+++ b/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
@@ -27,6 +27,7 @@
 import android.hardware.radio.network.PhysicalChannelConfig;
 import android.hardware.radio.network.SignalStrength;
 import android.hardware.radio.network.SuppSvcNotification;
+import android.hardware.radio.network.EmergencyRegResult;
 
 /**
  * Interface declaring unsolicited radio indications for network APIs.
@@ -190,4 +191,12 @@
      * @param rat Current new voice rat
      */
     void voiceRadioTechChanged(in RadioIndicationType type, in RadioTechnology rat);
+
+    /**
+     * Emergency Scan Results.
+     *
+     * @param type Type of radio indication
+     * @param result the result of the Emergency Network Scan
+     */
+    void emergencyNetworkScanResult(in RadioIndicationType type, in EmergencyRegResult result);
 }
diff --git a/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl b/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
index dcf0004..d98a31b 100644
--- a/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
+++ b/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
@@ -29,6 +29,7 @@
 import android.hardware.radio.network.RegStateResult;
 import android.hardware.radio.network.SignalStrength;
 import android.hardware.radio.network.UsageSetting;
+import android.hardware.radio.network.EmergencyRegResult;
 
 /**
  * Interface declaring response functions to solicited radio requests for network APIs.
@@ -572,4 +573,51 @@
      *   RadioError:SIM_ABSENT
      */
     oneway void getUsageSettingResponse(in RadioResponseInfo info, in UsageSetting usageSetting);
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     * @param regState the current registration state of the modem.
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:REQUEST_NOT_SUPPORTED
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:MODEM_ERR
+     *   RadioError:INVALID_ARGUMENTS
+     */
+    void setEmergencyModeResponse(in RadioResponseInfo info, in EmergencyRegResult regState);
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:REQUEST_NOT_SUPPORTED
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:MODEM_ERR
+     *   RadioError:INVALID_ARGUMENTS
+     */
+    void triggerEmergencyNetworkScanResponse(in RadioResponseInfo info);
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:REQUEST_NOT_SUPPORTED
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:MODEM_ERR
+     */
+    void exitEmergencyModeResponse(in RadioResponseInfo info);
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:REQUEST_NOT_SUPPORTED
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:MODEM_ERR
+     */
+    void cancelEmergencyNetworkScanResponse(in RadioResponseInfo info);
 }
diff --git a/sensors/2.1/default/apex/apex_manifest.json b/sensors/2.1/default/apex/apex_manifest.json
deleted file mode 100644
index 47e45ee..0000000
--- a/sensors/2.1/default/apex/apex_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.android.hardware.sensors",
-  "version": 1
-}
diff --git a/sensors/2.1/default/apex/com.android.hardware.sensors.rc b/sensors/2.1/default/apex/com.android.hardware.sensors.rc
deleted file mode 100644
index bd245b4..0000000
--- a/sensors/2.1/default/apex/com.android.hardware.sensors.rc
+++ /dev/null
@@ -1,7 +0,0 @@
-service vendor.sensors-hal-2-1-mock /apex/com.android.hardware.sensors/bin/hw/android.hardware.sensors@2.1-service.mock
-    interface android.hardware.sensors@2.0::ISensors default
-    interface android.hardware.sensors@2.1::ISensors default
-    class hal
-    user system
-    group system
-    rlimit rtprio 10 10
diff --git a/sensors/2.1/default/apex/file_contexts b/sensors/2.1/default/apex/file_contexts
deleted file mode 100644
index d0095c0..0000000
--- a/sensors/2.1/default/apex/file_contexts
+++ /dev/null
@@ -1,5 +0,0 @@
-(/.*)?							u:object_r:vendor_file:s0
-# Permission XMLs
-/etc/permissions(/.*)?					u:object_r:vendor_configs_file:s0
-# Service binary
-/bin/hw/android\.hardware\.sensors@2\.1-service\.mock	u:object_r:hal_sensors_default_exec:s0
diff --git a/sensors/aidl/default/Android.bp b/sensors/aidl/default/Android.bp
index 49841a4..3c66744 100644
--- a/sensors/aidl/default/Android.bp
+++ b/sensors/aidl/default/Android.bp
@@ -23,6 +23,16 @@
     default_applicable_licenses: ["hardware_interfaces_license"],
 }
 
+filegroup {
+    name: "sensors-default.rc",
+    srcs: ["sensors-default.rc"],
+}
+
+filegroup {
+    name: "sensors-default.xml",
+    srcs: ["sensors-default.xml"],
+}
+
 cc_library_static {
     name: "libsensorsexampleimpl",
     vendor: true,
@@ -47,8 +57,8 @@
 cc_binary {
     name: "android.hardware.sensors-service.example",
     relative_install_path: "hw",
-    init_rc: ["sensors-default.rc"],
-    vintf_fragments: ["sensors-default.xml"],
+    init_rc: [":sensors-default.rc"],
+    vintf_fragments: [":sensors-default.xml"],
     vendor: true,
     shared_libs: [
         "libbase",
diff --git a/sensors/2.1/default/apex/Android.bp b/sensors/aidl/default/apex/Android.bp
similarity index 78%
rename from sensors/2.1/default/apex/Android.bp
rename to sensors/aidl/default/apex/Android.bp
index 3345b92..ceb428b 100644
--- a/sensors/2.1/default/apex/Android.bp
+++ b/sensors/aidl/default/apex/Android.bp
@@ -13,9 +13,16 @@
     certificate: "com.android.hardware.sensors",
 }
 
+genrule {
+    name: "com.android.hardware.sensors.rc-gen",
+    srcs: [":sensors-default.rc"],
+    out: ["com.android.hardware.sensors.rc"],
+    cmd: "sed -E 's/\\/vendor/\\/apex\\/com.android.hardware.sensors/' $(in) > $(out)",
+}
+
 prebuilt_etc {
     name: "com.android.hardware.sensors.rc",
-    src: "com.android.hardware.sensors.rc",
+    src: ":com.android.hardware.sensors.rc-gen",
     installable: false,
 }
 
@@ -31,7 +38,7 @@
     updatable: false,
     // Install the apex in /vendor/apex
     soc_specific: true,
-    binaries: ["android.hardware.sensors@2.1-service.mock"],
+    binaries: ["android.hardware.sensors-service.example"],
     prebuilts: [
         "com.android.hardware.sensors.rc",
         "android.hardware.sensor.ambient_temperature.prebuilt.xml",
@@ -42,5 +49,5 @@
         "android.hardware.sensor.proximity.prebuilt.xml",
         "android.hardware.sensor.relative_humidity.prebuilt.xml",
     ],
-    vintf_fragments: [":android.hardware.sensors@2.1.xml"],
+    vintf_fragments: [":sensors-default.xml"],
 }
diff --git a/sensors/aidl/default/apex/apex_manifest.json b/sensors/aidl/default/apex/apex_manifest.json
new file mode 100644
index 0000000..659e739
--- /dev/null
+++ b/sensors/aidl/default/apex/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.sensors",
+    "version": 1
+}
diff --git a/sensors/2.1/default/apex/com.android.hardware.sensors.avbpubkey b/sensors/aidl/default/apex/com.android.hardware.sensors.avbpubkey
similarity index 100%
rename from sensors/2.1/default/apex/com.android.hardware.sensors.avbpubkey
rename to sensors/aidl/default/apex/com.android.hardware.sensors.avbpubkey
Binary files differ
diff --git a/sensors/2.1/default/apex/com.android.hardware.sensors.pem b/sensors/aidl/default/apex/com.android.hardware.sensors.pem
similarity index 100%
rename from sensors/2.1/default/apex/com.android.hardware.sensors.pem
rename to sensors/aidl/default/apex/com.android.hardware.sensors.pem
diff --git a/sensors/2.1/default/apex/com.android.hardware.sensors.pk8 b/sensors/aidl/default/apex/com.android.hardware.sensors.pk8
similarity index 100%
rename from sensors/2.1/default/apex/com.android.hardware.sensors.pk8
rename to sensors/aidl/default/apex/com.android.hardware.sensors.pk8
Binary files differ
diff --git a/sensors/2.1/default/apex/com.android.hardware.sensors.x509.pem b/sensors/aidl/default/apex/com.android.hardware.sensors.x509.pem
similarity index 100%
rename from sensors/2.1/default/apex/com.android.hardware.sensors.x509.pem
rename to sensors/aidl/default/apex/com.android.hardware.sensors.x509.pem
diff --git a/sensors/aidl/default/apex/file_contexts b/sensors/aidl/default/apex/file_contexts
new file mode 100644
index 0000000..27be16b
--- /dev/null
+++ b/sensors/aidl/default/apex/file_contexts
@@ -0,0 +1,5 @@
+(/.*)?							u:object_r:vendor_file:s0
+# Permission XMLs
+/etc/permissions(/.*)?					u:object_r:vendor_configs_file:s0
+# Service binary
+/bin/hw/android\.hardware\.sensors-service\.example	u:object_r:hal_sensors_default_exec:s0
\ No newline at end of file
diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
index 3e3a4d4..59b7939 100644
--- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
+++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp
@@ -144,9 +144,9 @@
     ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId(filterId));
     ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
     ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
-    mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
     ASSERT_TRUE(mDvrTests.startDvrPlayback());
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
+    mDvrTests.startPlaybackInputThread(dvrConf.playbackInputFile, dvrConf.settings.playback());
     ASSERT_TRUE(filterDataOutputTest());
     mDvrTests.stopPlaybackThread();
     ASSERT_TRUE(mFilterTests.stopFilter(filterId));
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/tv/tuner/aidl/vts/functional/FrontendTests.cpp b/tv/tuner/aidl/vts/functional/FrontendTests.cpp
index a1f51df..2ff0c3d 100644
--- a/tv/tuner/aidl/vts/functional/FrontendTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FrontendTests.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include <aidl/android/hardware/tv/tuner/Result.h>
-
 #include "FrontendTests.h"
 
+#include <aidl/android/hardware/tv/tuner/Result.h>
+
 ndk::ScopedAStatus FrontendCallback::onEvent(FrontendEventType frontendEventType) {
     android::Mutex::Autolock autoLock(mMsgLock);
     ALOGD("[vts] frontend event received. Type: %d", frontendEventType);
@@ -323,7 +323,10 @@
         FrontendStatusType type = statusTypes[i];
         switch (type) {
             case FrontendStatusType::MODULATIONS: {
-                // TODO: verify modulations
+                ASSERT_TRUE(std::equal(
+                        realStatuses[i].get<FrontendStatus::Tag::modulations>().begin(),
+                        realStatuses[i].get<FrontendStatus::Tag::modulations>().end(),
+                        expectStatuses[i].get<FrontendStatus::Tag::modulations>().begin()));
                 break;
             }
             case FrontendStatusType::BERS: {
@@ -340,11 +343,13 @@
                 break;
             }
             case FrontendStatusType::GUARD_INTERVAL: {
-                // TODO: verify interval
+                ASSERT_TRUE(realStatuses[i].get<FrontendStatus::Tag::interval>() ==
+                            expectStatuses[i].get<FrontendStatus::Tag::interval>());
                 break;
             }
             case FrontendStatusType::TRANSMISSION_MODE: {
-                // TODO: verify tranmission mode
+                ASSERT_TRUE(realStatuses[i].get<FrontendStatus::Tag::transmissionMode>() ==
+                            expectStatuses[i].get<FrontendStatus::Tag::transmissionMode>());
                 break;
             }
             case FrontendStatusType::UEC: {
@@ -379,7 +384,8 @@
                 break;
             }
             case FrontendStatusType::ROLL_OFF: {
-                // TODO: verify roll off
+                ASSERT_TRUE(realStatuses[i].get<FrontendStatus::Tag::rollOff>() ==
+                            expectStatuses[i].get<FrontendStatus::Tag::rollOff>());
                 break;
             }
             case FrontendStatusType::IS_MISO: {
@@ -599,9 +605,10 @@
     ASSERT_TRUE(tuneFrontend(frontendConf, false /*testWithDemux*/));
 
     // TODO: find a better way to push all frontend status types
-    for (int32_t i = 0; i < static_cast<int32_t>(FrontendStatusType::ATSC3_ALL_PLP_INFO); i++) {
+    for (int32_t i = 0; i <= static_cast<int32_t>(FrontendStatusType::ATSC3_ALL_PLP_INFO); i++) {
         allTypes.push_back(static_cast<FrontendStatusType>(i));
     }
+
     ndk::ScopedAStatus status = mFrontend->getFrontendStatusReadiness(allTypes, &readiness);
     ASSERT_TRUE(status.isOk());
     ASSERT_TRUE(readiness.size() == allTypes.size());
diff --git a/uwb/aidl/Android.bp b/uwb/aidl/Android.bp
index 52f0605..7dc2b7f 100755
--- a/uwb/aidl/Android.bp
+++ b/uwb/aidl/Android.bp
@@ -47,6 +47,7 @@
 
 aidl_interface {
     name: "android.hardware.uwb.fira_android",
+    owner: "google",
     vendor_available: true,
     srcs: ["android/hardware/uwb/fira_android/*.aidl"],
     stability: "vintf",
diff --git a/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl b/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
index cbe2068..17e4381 100644
--- a/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
+++ b/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
@@ -44,4 +44,5 @@
   CCC_SUPPORTED_PULSE_SHAPE_COMBOS = 166,
   CCC_SUPPORTED_RAN_MULTIPLIER = 167,
   SUPPORTED_AOA_RESULT_REQ_ANTENNA_INTERLEAVING = 227,
+  SUPPORTED_MIN_RANGING_INTERVAL_MS = 228,
 }
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/uwb/aidl/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl b/uwb/aidl/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
index 0140fdd..3961eda 100644
--- a/uwb/aidl/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
+++ b/uwb/aidl/android/hardware/uwb/fira_android/UwbVendorCapabilityTlvTypes.aidl
@@ -150,4 +150,9 @@
      *  0 - Feature not supported.
      */
     SUPPORTED_AOA_RESULT_REQ_ANTENNA_INTERLEAVING = 0xE3,
+
+    /**
+     * 4 byte value to indicate supported min ranging interval in ms.
+     */
+    SUPPORTED_MIN_RANGING_INTERVAL_MS = 0xE4,
 }
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