Merge "Link in libtonemap into composer VTS"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1d74e21
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vscode/
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
new file mode 100644
index 0000000..c172674
--- /dev/null
+++ b/audio/aidl/Android.bp
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+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"],
+}
+
+aidl_interface {
+    name: "android.hardware.audio.common",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/audio/common/PlaybackTrackMetadata.aidl",
+        "android/hardware/audio/common/RecordTrackMetadata.aidl",
+        "android/hardware/audio/common/SinkMetadata.aidl",
+        "android/hardware/audio/common/SourceMetadata.aidl",
+    ],
+    imports: [
+        "android.media.audio.common.types",
+    ],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+    versions: [
+    ],
+}
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/PlaybackTrackMetadata.aidl
similarity index 76%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/PlaybackTrackMetadata.aidl
index 2b872ab..be4941c 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/PlaybackTrackMetadata.aidl
@@ -31,14 +31,13 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.audio.common;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable PlaybackTrackMetadata {
+  android.media.audio.common.AudioUsage usage = android.media.audio.common.AudioUsage.INVALID;
+  android.media.audio.common.AudioContentType contentType = android.media.audio.common.AudioContentType.UNKNOWN;
+  float gain;
+  android.media.audio.common.AudioChannelLayout channelMask;
+  @nullable android.media.audio.common.AudioDevice sourceDevice;
+  @utf8InCpp String[] tags;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/RecordTrackMetadata.aidl
similarity index 79%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/RecordTrackMetadata.aidl
index 2b872ab..8f667d1 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/RecordTrackMetadata.aidl
@@ -31,14 +31,12 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.audio.common;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable RecordTrackMetadata {
+  android.media.audio.common.AudioSource source = android.media.audio.common.AudioSource.SYS_RESERVED_INVALID;
+  float gain;
+  @nullable android.media.audio.common.AudioDevice destinationDevice;
+  android.media.audio.common.AudioChannelLayout channelMask;
+  @utf8InCpp String[] tags;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SinkMetadata.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SinkMetadata.aidl
index 2b872ab..270147d 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SinkMetadata.aidl
@@ -31,14 +31,8 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.audio.common;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable SinkMetadata {
+  android.hardware.audio.common.RecordTrackMetadata[] tracks;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SourceMetadata.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SourceMetadata.aidl
index 2b872ab..2d4a982 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.common/current/android/hardware/audio/common/SourceMetadata.aidl
@@ -31,14 +31,8 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.audio.common;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable SourceMetadata {
+  android.hardware.audio.common.PlaybackTrackMetadata[] tracks;
 }
diff --git a/audio/aidl/android/hardware/audio/common/PlaybackTrackMetadata.aidl b/audio/aidl/android/hardware/audio/common/PlaybackTrackMetadata.aidl
new file mode 100644
index 0000000..9ce1e1f
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/common/PlaybackTrackMetadata.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package android.hardware.audio.common;
+
+import android.media.audio.common.AudioChannelLayout;
+import android.media.audio.common.AudioContentType;
+import android.media.audio.common.AudioDevice;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Metadata of a playback track for an output stream.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable PlaybackTrackMetadata {
+    AudioUsage usage = AudioUsage.INVALID;
+    AudioContentType contentType = AudioContentType.UNKNOWN;
+    /**
+     * Non-negative linear gain (scaling) applied to track samples.
+     * 0 means muted, 1 is unity gain, 2 means double amplitude, etc.
+     */
+    float gain;
+    AudioChannelLayout channelMask;
+    /**
+     * Indicates the source of an output stream, can be left unspecified.
+     */
+    @nullable AudioDevice sourceDevice;
+    /**
+     * Tags from AudioTrack audio attributes. Tag is an additional use case
+     * qualifier complementing AudioUsage and AudioContentType. Tags are set by
+     * vendor specific applications and must be prefixed by "VX_". Vendor must
+     * namespace their tag names to avoid conflicts, for example:
+     * "VX_GOOGLE_VR". At least 3 characters are required for the vendor
+     * namespace.
+     */
+    @utf8InCpp String[] tags;
+}
diff --git a/audio/aidl/android/hardware/audio/common/RecordTrackMetadata.aidl b/audio/aidl/android/hardware/audio/common/RecordTrackMetadata.aidl
new file mode 100644
index 0000000..dfd88f1
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/common/RecordTrackMetadata.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package android.hardware.audio.common;
+
+import android.media.audio.common.AudioChannelLayout;
+import android.media.audio.common.AudioContentType;
+import android.media.audio.common.AudioDevice;
+import android.media.audio.common.AudioSource;
+
+/**
+ * Metadata of a record track for an input stream.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable RecordTrackMetadata {
+    AudioSource source = AudioSource.SYS_RESERVED_INVALID;
+    /**
+     * Non-negative linear gain (scaling) applied to track samples.
+     * 0 means muted, 1 is unity gain, 2 means double amplitude, etc.
+     */
+    float gain;
+    /**
+     * Indicates the destination of an input stream, can be left unspecified.
+     */
+    @nullable AudioDevice destinationDevice;
+    AudioChannelLayout channelMask;
+    /**
+     * Tags from AudioRecord audio attributes. Tag is an additional use case
+     * qualifier complementing AudioUsage and AudioContentType. Tags are set by
+     * vendor specific applications and must be prefixed by "VX_". Vendor must
+     * namespace their tag names to avoid conflicts, for example:
+     * "VX_GOOGLE_VR". At least 3 characters are required for the vendor
+     * namespace.
+     */
+    @utf8InCpp String[] tags;
+}
diff --git a/audio/aidl/android/hardware/audio/common/SinkMetadata.aidl b/audio/aidl/android/hardware/audio/common/SinkMetadata.aidl
new file mode 100644
index 0000000..188c847
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/common/SinkMetadata.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.hardware.audio.common;
+
+import android.hardware.audio.common.RecordTrackMetadata;
+
+/**
+ * Metadata of record tracks for an input stream.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable SinkMetadata {
+    RecordTrackMetadata[] tracks;
+}
diff --git a/audio/aidl/android/hardware/audio/common/SourceMetadata.aidl b/audio/aidl/android/hardware/audio/common/SourceMetadata.aidl
new file mode 100644
index 0000000..e9f23c6
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/common/SourceMetadata.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.hardware.audio.common;
+
+import android.hardware.audio.common.PlaybackTrackMetadata;
+
+/**
+ * Metadata of playback tracks for an output stream.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable SourceMetadata {
+    PlaybackTrackMetadata[] tracks;
+}
diff --git a/audio/core/all-versions/vts/functional/7.0/Generators.cpp b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
index d2ba339..8c92cbd 100644
--- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
@@ -102,6 +102,9 @@
             if (mixPort.getRole() != xsd::Role::source) continue;  // not an output profile
             auto [flags, isOffload] = generateOutFlags(mixPort);
             for (const auto& profile : mixPort.getProfile()) {
+                if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+                    !profile.hasChannelMasks())
+                    continue;
                 auto configs = combineAudioConfig(profile.getChannelMasks(),
                                                   profile.getSamplingRates(), profile.getFormat());
                 for (auto& config : configs) {
@@ -231,6 +234,9 @@
                                std::back_inserter(flags), [](auto flag) { return toString(flag); });
             }
             for (const auto& profile : mixPort.getProfile()) {
+                if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+                    !profile.hasChannelMasks())
+                    continue;
                 auto configs = combineAudioConfig(profile.getChannelMasks(),
                                                   profile.getSamplingRates(), profile.getFormat());
                 for (const auto& config : configs) {
diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp
index 7c25c23..b99ed43 100644
--- a/audio/core/all-versions/vts/functional/Android.bp
+++ b/audio/core/all-versions/vts/functional/Android.bp
@@ -245,6 +245,7 @@
     data: [
         "tests/apm_config_no_vx_7_0.xml",
         "tests/apm_config_with_vx_7_0.xml",
+        "tests/apm_config_b_204314749_7_0.xml",
     ],
     test_config: "tests/HalAudioV7_0GeneratorTest.xml",
 }
diff --git a/audio/core/all-versions/vts/functional/tests/HalAudioV7_0GeneratorTest.xml b/audio/core/all-versions/vts/functional/tests/HalAudioV7_0GeneratorTest.xml
index 2e79455..3dc5b33 100644
--- a/audio/core/all-versions/vts/functional/tests/HalAudioV7_0GeneratorTest.xml
+++ b/audio/core/all-versions/vts/functional/tests/HalAudioV7_0GeneratorTest.xml
@@ -24,6 +24,7 @@
         <option name="cleanup" value="true" />
         <option name="push" value="apm_config_no_vx_7_0.xml->/data/local/tmp/apm_config_no_vx.xml" />
         <option name="push" value="apm_config_with_vx_7_0.xml->/data/local/tmp/apm_config_with_vx.xml" />
+        <option name="push" value="apm_config_b_204314749_7_0.xml->/data/local/tmp/apm_config_b_204314749_7_0.xml" />
         <option name="push" value="HalAudioV7_0GeneratorTest->/data/local/tmp/HalAudioV7_0GeneratorTest" />
     </target_preparer>
 
diff --git a/audio/core/all-versions/vts/functional/tests/apm_config_b_204314749_7_0.xml b/audio/core/all-versions/vts/functional/tests/apm_config_b_204314749_7_0.xml
new file mode 100644
index 0000000..5bdca9a
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/tests/apm_config_b_204314749_7_0.xml
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<audioPolicyConfiguration version="7.0" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <!-- version section contains a “version” tag in the form “major.minor” e.g. version=”1.0” -->
+
+    <!-- Global configuration Decalaration -->
+    <globalConfiguration speaker_drc_enabled="false"/>
+
+
+    <!-- Modules section:
+        There is one section per audio HW module present on the platform.
+        Each module section will contains two mandatory tags for audio HAL “halVersion” and “name”.
+        The module names are the same as in current .conf file:
+                “primary”, “A2DP”, “remote_submix”, “USB”
+        Each module will contain the following sections:
+        “devicePorts”: a list of device descriptors for all input and output devices accessible via this
+        module.
+        This contains both permanently attached devices and removable devices.
+        “mixPorts”: listing all output and input streams exposed by the audio HAL
+        “routes”: list of possible connections between input and output devices or between stream and
+        devices.
+            "route": is defined by an attribute:
+                -"type": <mux|mix> means all sources are mutual exclusive (mux) or can be mixed (mix)
+                -"sink": the sink involved in this route
+                -"sources": all the sources than can be connected to the sink via vis route
+        “attachedDevices”: permanently attached devices.
+        The attachedDevices section is a list of devices names. The names correspond to device names
+        defined in <devicePorts> section.
+        “defaultOutputDevice”: device to be used by default when no policy rule applies
+    -->
+    <modules>
+        <!-- Primary Audio HAL -->
+        <module name="primary" halVersion="3.0">
+            <attachedDevices>
+                <item>Speaker</item>
+                <item>Built-In Mic</item>
+                <item>Built-In Back Mic</item>
+                <item>Echo Reference</item>
+                <item>Tuner</item>
+            </attachedDevices>
+            <defaultOutputDevice>Speaker</defaultOutputDevice>
+            <mixPorts>
+                <mixPort name="primary output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </mixPort>
+                <mixPort name="tunnel pcm" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT AUDIO_OUTPUT_FLAG_HW_AV_SYNC">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="32000 44100 48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </mixPort>
+                <mixPort name="direct pcm" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="32000 44100 48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </mixPort>
+                <mixPort name="direct output" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT">
+                    <profile name=""/>
+                </mixPort>
+                <mixPort name="tunnel direct output" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT AUDIO_OUTPUT_FLAG_HW_AV_SYNC">
+                    <profile name=""/>
+                </mixPort>
+                <mixPort name="mmap_no_irq_out" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT AUDIO_OUTPUT_FLAG_MMAP_NOIRQ">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                            samplingRates="48000"
+                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </mixPort>
+                <mixPort name="primary input" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO"/>
+                </mixPort>
+                <mixPort name="tunnel a2dp" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT AUDIO_OUTPUT_FLAG_HW_AV_SYNC">
+                    <profile name=""/>
+                </mixPort>
+                <mixPort name="direct a2dp" role="source" flags="AUDIO_OUTPUT_FLAG_DIRECT">
+                    <profile name=""/>
+                </mixPort>
+                <mixPort name="echo reference" role="sink">
+                    <profile name="echo_reference" format="AUDIO_FORMAT_PCM_32_BIT"
+                             samplingRates="48000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO"/>
+                </mixPort>
+                <mixPort name="built-in mic" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_32_BIT"
+                             samplingRates="16000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO"/>
+                </mixPort>
+                <mixPort name="ble_in" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
+                </mixPort>
+            </mixPorts>
+            <devicePorts>
+                <!-- Output devices declaration, i.e. Sink DEVICE PORT -->
+                <devicePort tagName="Speaker" role="sink" type="AUDIO_DEVICE_OUT_SPEAKER">
+                    <gains>
+                        <gain name="gain_1" mode="AUDIO_GAIN_MODE_JOINT"
+                              minValueMB="-10000"
+                              maxValueMB="0"
+                              defaultValueMB="-6000"
+                              stepValueMB="100"/>
+                    </gains>
+                </devicePort>
+                <devicePort tagName="HDMI Out" type="AUDIO_DEVICE_OUT_AUX_DIGITAL" role="sink">
+                </devicePort>
+                <devicePort tagName="Tuner" role="source" type="AUDIO_DEVICE_IN_TV_TUNER">
+                    <gains>
+                        <gain name="gain_1" mode="AUDIO_GAIN_MODE_JOINT"
+                              minValueMB="-10000"
+                              maxValueMB="0"
+                              defaultValueMB="-6000"
+                              stepValueMB="100"/>
+                    </gains>
+                </devicePort>
+                <devicePort tagName="Wired Headset" type="AUDIO_DEVICE_OUT_WIRED_HEADSET" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </devicePort>
+                <devicePort tagName="Wired Headphones" type="AUDIO_DEVICE_OUT_WIRED_HEADPHONE" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </devicePort>
+                <devicePort tagName="BT SCO" type="AUDIO_DEVICE_OUT_BLUETOOTH_SCO" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 16000" channelMasks="AUDIO_CHANNEL_OUT_MONO"/>
+                </devicePort>
+                <devicePort tagName="BT SCO Headset" type="AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 16000" channelMasks="AUDIO_CHANNEL_OUT_MONO"/>
+                </devicePort>
+                <devicePort tagName="BT A2DP Out" type="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="44100"
+                             channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </devicePort>
+                <devicePort tagName="BT A2DP Headphones" type="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="44100"
+                             channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </devicePort>
+                <devicePort tagName="BT A2DP Speaker" type="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER" role="sink">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="44100"
+                             channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+                </devicePort>
+
+                <devicePort tagName="Built-In Mic" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source" address="top">
+                    <profile name="" format="AUDIO_FORMAT_PCM_32_BIT"
+                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO AUDIO_CHANNEL_IN_FRONT_BACK"/>
+                </devicePort>
+                <devicePort tagName="Built-In Back Mic" type="AUDIO_DEVICE_IN_BACK_MIC" role="source">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO AUDIO_CHANNEL_IN_FRONT_BACK"/>
+                </devicePort>
+                <devicePort tagName="Wired Headset Mic" type="AUDIO_DEVICE_IN_WIRED_HEADSET" role="source">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO AUDIO_CHANNEL_IN_FRONT_BACK"/>
+                </devicePort>
+                <devicePort tagName="BT SCO Headset Mic" type="AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET" role="source">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="8000 16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
+                </devicePort>
+                <devicePort tagName="Echo Reference" type="AUDIO_DEVICE_IN_ECHO_REFERENCE" role="source">
+                    <profile name="echo_reference" format="AUDIO_FORMAT_PCM_32_BIT"
+                             samplingRates="48000"
+                             channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
+                </devicePort>
+                <devicePort tagName="BLE-In" type="AUDIO_DEVICE_IN_BLUETOOTH_BLE" role="source">
+                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                             samplingRates="16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
+                </devicePort>
+            </devicePorts>
+            <!-- route declaration, i.e. list all available sources for a given sink -->
+            <routes>
+                <route type="mix" sink="HDMI Out"
+                       sources="primary output,tunnel pcm,direct output,Tuner,mmap_no_irq_out,tunnel direct output"/>
+                <route type="mix" sink="Speaker"
+                       sources="primary output,tunnel pcm,direct pcm,BT SCO Headset Mic,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="BT SCO"
+                       sources="primary output,BT SCO Headset Mic,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="BT SCO Headset"
+                       sources="primary output,BT SCO Headset Mic,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="Wired Headset"
+                       sources="primary output,tunnel pcm,BT SCO Headset Mic,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="Wired Headphones"
+                       sources="primary output,tunnel pcm,BT SCO Headset Mic,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="primary input"
+                       sources="Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic,Tuner"/>
+                <route type="mix" sink="BT A2DP Out"
+                       sources="primary output,tunnel a2dp,direct a2dp,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="BT A2DP Headphones"
+                       sources="primary output,tunnel a2dp,direct a2dp,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="BT A2DP Speaker"
+                       sources="primary output,tunnel a2dp,direct a2dp,Tuner,mmap_no_irq_out"/>
+                <route type="mix" sink="echo reference"
+                       sources="Echo Reference"/>
+                <route type="mix" sink="built-in mic"
+                       sources="Built-In Mic"/>
+                <route type="mix" sink="ble_in"
+                       sources="BLE-In"/>
+            </routes>
+
+        </module>
+
+        <!-- A2dp Audio HAL -->
+        <!-- <xi:include href="a2dp_audio_policy_configuration.xml"/> -->
+
+        <!-- Usb Audio HAL -->
+        <!-- <xi:include href="usb_audio_policy_configuration.xml"/> -->
+
+        <!-- Remote Submix Audio HAL -->
+        <!-- <xi:include href="r_submix_audio_policy_configuration.xml"/> -->
+
+        <!-- Hearing aid Audio HAL -->
+        <!-- <xi:include href="hearing_aid_audio_policy_configuration.xml"/> -->
+
+        <!-- MSD Audio HAL (optional) -->
+        <!-- <xi:include href="msd_audio_policy_configuration.xml"/> -->
+
+    </modules>
+    <!-- End of Modules section -->
+
+    <!-- Volume section -->
+
+    <!-- <xi:include href="audio_policy_volumes.xml"/> -->
+    <!-- <xi:include href="default_volume_tables.xml"/> -->
+
+    <!-- End of Volume section -->
+
+    <!-- Surround Sound configuration -->
+
+    <surroundSound>
+    <!-- Each of the listed formats gets an entry in Surround Settings dialog on TV devices.
+        There must be a corresponding Java ENCODING_... constant defined in AudioFormat.java,
+        and a display name defined in AudioFormat.toDisplayName. For the formats that don't
+        need a dedicated Surrond Settings dialog entry, a subformats list has to be used. -->
+        <formats>
+            <format name="AUDIO_FORMAT_AC3" />
+            <format name="AUDIO_FORMAT_E_AC3" />
+            <format name="AUDIO_FORMAT_E_AC3_JOC" />
+            <format name="AUDIO_FORMAT_DTS" />
+        </formats>
+    </surroundSound>
+
+    <!-- End of Surround Sound configuration -->
+
+</audioPolicyConfiguration>
diff --git a/audio/core/all-versions/vts/functional/tests/generators_tests.cpp b/audio/core/all-versions/vts/functional/tests/generators_tests.cpp
index 583ff01..7caa712 100644
--- a/audio/core/all-versions/vts/functional/tests/generators_tests.cpp
+++ b/audio/core/all-versions/vts/functional/tests/generators_tests.cpp
@@ -128,5 +128,11 @@
 }
 
 // Target file names are the same for all versions, see 'HalAudioVx_0GeneratorTest.xml' test configs
+// clang-format off
 INSTANTIATE_TEST_SUITE_P(Generators, GeneratorsTest,
-                         ::testing::Values("apm_config_no_vx.xml", "apm_config_with_vx.xml"));
+                         ::testing::Values("apm_config_no_vx.xml", "apm_config_with_vx.xml"
+#if MAJOR_VERSION == 7
+                                         , "apm_config_b_204314749_7_0.xml"
+#endif
+                                           ));
+// clang-format on
diff --git a/automotive/can/1.0/default/Android.bp b/automotive/can/1.0/default/Android.bp
index c0c17e2..163fdb7 100644
--- a/automotive/can/1.0/default/Android.bp
+++ b/automotive/can/1.0/default/Android.bp
@@ -64,4 +64,5 @@
         "android.hardware.automotive@libc++fs",
         "libnl++",
     ],
+    vintf_fragments: ["manifest_android.hardware.automotive.can@1.0.xml"],
 }
diff --git a/automotive/can/1.0/default/manifest_android.hardware.automotive.can@1.0.xml b/automotive/can/1.0/default/manifest_android.hardware.automotive.can@1.0.xml
new file mode 100644
index 0000000..2078ce5
--- /dev/null
+++ b/automotive/can/1.0/default/manifest_android.hardware.automotive.can@1.0.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<manifest version="1.0" type="device" >
+    <hal format="hidl">
+        <name>android.hardware.automotive.can</name>
+        <transport>hwbinder</transport>
+        <fqname>@1.0::ICanController/socketcan</fqname>
+    </hal>
+</manifest>
diff --git a/automotive/vehicle/TEST_MAPPING b/automotive/vehicle/TEST_MAPPING
index 9924581..3696351 100644
--- a/automotive/vehicle/TEST_MAPPING
+++ b/automotive/vehicle/TEST_MAPPING
@@ -14,6 +14,12 @@
     },
     {
       "name": "FakeVehicleHalValueGeneratorsTest"
+    },
+    {
+      "name": "FakeObd2FrameTest"
+    },
+    {
+      "name": "FakeUserHalTest"
     }
   ]
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserInfo.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserInfo.aidl
index b9cf894..b99272b 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserInfo.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserInfo.aidl
@@ -35,5 +35,11 @@
 @VintfStability
 parcelable UserInfo {
   int userId = 0;
-  android.hardware.automotive.vehicle.UserFlags flags = android.hardware.automotive.vehicle.UserFlags.NONE;
+  int flags;
+  const int USER_FLAG_SYSTEM = 1;
+  const int USER_FLAG_GUEST = 2;
+  const int USER_FLAG_EPHEMERAL = 4;
+  const int USER_FLAG_ADMIN = 8;
+  const int USER_FLAG_DISABLED = 16;
+  const int USER_FLAG_PROFILE = 32;
 }
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserFlags.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserFlags.aidl
deleted file mode 100644
index caa62df..0000000
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserFlags.aidl
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-package android.hardware.automotive.vehicle;
-
-/**
- * Flags used to define the characteristics of an Android user.
- */
-@VintfStability
-@Backing(type="int")
-enum UserFlags {
-    /**
-     * No flags.
-     */
-    NONE = 0x0,
-    /**
-     * System user.
-     * On automotive, that user is always running, although never on foreground (except during
-     * boot or exceptional circumstances).
-     */
-    SYSTEM = 0x01,
-    /**
-     * Guest users have restrictions.
-     */
-    GUEST = 0x02,
-    /**
-     * Ephemeral users have non-persistent state.
-     */
-    EPHEMERAL = 0x04,
-    /**
-     * Admin users have additional privileges such as permission to create other users.
-     */
-    ADMIN = 0x08,
-    /**
-     * Disabled users are marked for deletion.
-     */
-    DISABLED = 0x10,
-    /**
-     * Profile user is a profile of another user.
-     */
-    PROFILE = 0x20,
-}
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserInfo.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserInfo.aidl
index e96fa37..e4ecc4b 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserInfo.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/UserInfo.aidl
@@ -16,13 +16,44 @@
 
 package android.hardware.automotive.vehicle;
 
-import android.hardware.automotive.vehicle.UserFlags;
-
 /**
  * Information about a specific Android user.
  */
 @VintfStability
 parcelable UserInfo {
+    /**
+     * System user.
+     *
+     * On automotive, that user is always running, although never on foreground (except during
+     * boot or exceptional circumstances).
+     */
+    const int USER_FLAG_SYSTEM = 0x01;
+    /**
+     * Guest users have restrictions.
+     */
+    const int USER_FLAG_GUEST = 0x02;
+    /**
+     * Ephemeral users have non-persistent state.
+     */
+    const int USER_FLAG_EPHEMERAL = 0x04;
+    /**
+     * Admin users have additional privileges such as permission to create other users.
+     */
+    const int USER_FLAG_ADMIN = 0x08;
+    /**
+     * Disabled users are marked for deletion.
+     */
+    const int USER_FLAG_DISABLED = 0x10;
+    /**
+     * Profile user is a profile of another user.
+     */
+    const int USER_FLAG_PROFILE = 0x20;
+    /*
+     * The user ID.
+     */
     int userId = 0;
-    UserFlags flags = UserFlags.NONE;
+    /*
+     * Bitmask for the user flags defined above (USER_FLAG_*).
+     */
+    int flags;
 }
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/Android.bp
index 4735313..ab223d3 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/Android.bp
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/Android.bp
@@ -25,7 +25,10 @@
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
     defaults: ["VehicleHalDefaults"],
-    static_libs: ["VehicleHalUtils"],
+    static_libs: [
+        "VehicleHalUtils",
+        "FakeObd2Frame",
+    ],
     shared_libs: [
         "libjsoncpp",
     ],
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 521ce45..ae92797 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/src/JsonFakeValueGenerator.cpp
@@ -22,6 +22,7 @@
 #include <type_traits>
 #include <typeinfo>
 
+#include <Obd2SensorStore.h>
 #include <VehicleUtils.h>
 #include <android/binder_enums.h>
 #include <utils/Log.h>
@@ -42,16 +43,6 @@
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 
-template <class T>
-int getLastIndex() {
-    auto range = ::ndk::enum_range<T>();
-    auto it = range.begin();
-    while (std::next(it) != range.end()) {
-        it++;
-    }
-    return toInt(*it);
-}
-
 bool isDiagnosticProperty(int32_t prop) {
     return prop == toInt(VehicleProperty::OBD2_LIVE_FRAME) ||
            prop == toInt(VehicleProperty::OBD2_FREEZE_FRAME);
@@ -84,9 +75,10 @@
 }
 
 std::vector<uint8_t> generateDiagnosticBytes(const RawPropValues& diagnosticValue) {
-    size_t lastIntegerSensorIndex =
-            static_cast<size_t>(getLastIndex<DiagnosticIntegerSensorIndex>());
-    size_t lastFloatSensorIndex = static_cast<size_t>(getLastIndex<DiagnosticFloatSensorIndex>());
+    size_t lastIntegerSensorIndex = static_cast<size_t>(
+            obd2frame::Obd2SensorStore::getLastIndex<DiagnosticIntegerSensorIndex>());
+    size_t lastFloatSensorIndex = static_cast<size_t>(
+            obd2frame::Obd2SensorStore::getLastIndex<DiagnosticFloatSensorIndex>());
 
     size_t byteSize = (lastIntegerSensorIndex + lastFloatSensorIndex + 2);
     std::vector<uint8_t> bytes((byteSize + 7) / 8);
diff --git a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/test/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/test/Android.bp
index d3d3a10..ac8db44 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/test/Android.bp
+++ b/automotive/vehicle/aidl/impl/fake_impl/GeneratorHub/test/Android.bp
@@ -26,6 +26,7 @@
     static_libs: [
         "VehicleHalUtils",
         "FakeVehicleHalValueGenerators",
+        "FakeObd2Frame",
     ],
     shared_libs: [
         "libjsoncpp",
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/hardware/Android.bp
index d614c6b..dfc2efc 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/Android.bp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/Android.bp
@@ -22,6 +22,7 @@
     name: "FakeVehicleHardware",
     vendor: true,
     srcs: ["src/*.cpp"],
+    cflags: ["-DENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING"],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
     defaults: ["VehicleHalDefaults"],
@@ -30,6 +31,14 @@
         "VehicleHalDefaultConfig",
     ],
     export_header_lib_headers: ["IVehicleHardware"],
-    static_libs: ["VehicleHalUtils"],
+    static_libs: [
+        "VehicleHalUtils",
+        "FakeVehicleHalValueGenerators",
+        "FakeObd2Frame",
+        "FakeUserHal",
+    ],
+    shared_libs: [
+        "libjsoncpp",
+    ],
     export_static_lib_headers: ["VehicleHalUtils"],
 }
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 dee36f4..46a526c 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -18,9 +18,12 @@
 #define android_hardware_automotive_vehicle_aidl_impl_fake_impl_hardware_include_FakeVehicleHardware_H_
 
 #include <DefaultConfig.h>
+#include <FakeObd2Frame.h>
+#include <FakeUserHal.h>
 #include <IVehicleHardware.h>
 #include <VehicleHalTypes.h>
 #include <VehiclePropertyStore.h>
+#include <android-base/result.h>
 #include <android-base/thread_annotations.h>
 
 #include <map>
@@ -82,17 +85,45 @@
     void registerOnPropertySetErrorEvent(OnPropertySetErrorCallback&& callback) override;
 
   private:
-    void storePropInitialValue(const defaultconfig::ConfigDeclaration& config);
-    void init(std::shared_ptr<VehiclePropValuePool> valuePool);
-    void onValueChangeCallback(
-            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+    // Expose private methods to unit test.
+    friend class FakeVehicleHardwareTestHelper;
 
-    std::unique_ptr<VehiclePropertyStore> mServerSidePropStore;
     // mValuePool is also used in mServerSidePropStore.
-    std::shared_ptr<VehiclePropValuePool> mValuePool;
+    const std::shared_ptr<VehiclePropValuePool> mValuePool;
+    const std::shared_ptr<VehiclePropertyStore> mServerSidePropStore;
+    const std::unique_ptr<obd2frame::FakeObd2Frame> mFakeObd2Frame;
+    const std::unique_ptr<FakeUserHal> mFakeUserHal;
     std::mutex mCallbackLock;
     OnPropertyChangeCallback mOnPropertyChangeCallback GUARDED_BY(mCallbackLock);
     OnPropertySetErrorCallback mOnPropertySetErrorCallback GUARDED_BY(mCallbackLock);
+
+    void init();
+    // Stores the initial value to property store.
+    void storePropInitialValue(const defaultconfig::ConfigDeclaration& config);
+    // The callback that would be called when a vehicle property value change happens.
+    void onValueChangeCallback(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+    // If property "persist.vendor.vhal_init_value_override" is set to true, override the properties
+    // using config files in 'overrideDir'.
+    void maybeOverrideProperties(const char* overrideDir);
+    // Override the properties using config files in 'overrideDir'.
+    void overrideProperties(const char* overrideDir);
+
+    ::android::base::Result<void> maybeSetSpecialValue(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value,
+            bool* isSpecialValue);
+    ::android::base::Result<VehiclePropValuePool::RecyclableType> maybeGetSpecialValue(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value,
+            bool* isSpecialValue) const;
+    ::android::base::Result<void> setApPowerStateReport(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+    VehiclePropValuePool::RecyclableType createApPowerStateReq(
+            ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq state);
+    ::android::base::Result<void> setUserHalProp(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+    ::android::base::Result<VehiclePropValuePool::RecyclableType> getUserHalProp(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
+    bool isHvacPropAndHvacNotAvailable(int32_t propId);
 };
 
 }  // 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 f8bf7de..104147a 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -17,11 +17,20 @@
 #include "FakeVehicleHardware.h"
 
 #include <DefaultConfig.h>
+#include <FakeObd2Frame.h>
+#include <JsonFakeValueGenerator.h>
+#include <PropertyUtils.h>
 #include <VehicleHalTypes.h>
 #include <VehicleUtils.h>
+#include <android-base/properties.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
 
+#include <dirent.h>
+#include <sys/types.h>
+#include <fstream>
+#include <regex>
+#include <unordered_set>
 #include <vector>
 
 namespace android {
@@ -30,16 +39,52 @@
 namespace vehicle {
 namespace fake {
 
+namespace {
+
 using ::aidl::android::hardware::automotive::vehicle::GetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::GetValueResult;
 using ::aidl::android::hardware::automotive::vehicle::RawPropValues;
 using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
 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::VehiclePropConfig;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyGroup;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 
+using ::android::base::Error;
+using ::android::base::Result;
+
+const char* VENDOR_OVERRIDE_DIR = "/vendor/etc/automotive/vhaloverride/";
+const char* OVERRIDE_PROPERTY = "persist.vendor.vhal_init_value_override";
+
+template <class T>
+StatusCode getErrorCode(const Result<T>& result) {
+    if (result.ok()) {
+        return StatusCode::OK;
+    }
+    return static_cast<StatusCode>(result.error().code());
+}
+
+template <class T>
+int getIntErrorCode(const Result<T>& result) {
+    return toInt(getErrorCode(result));
+}
+
+template <class T>
+std::string getErrorMsg(const Result<T>& result) {
+    if (result.ok()) {
+        return "";
+    }
+    return result.error().message();
+}
+
+}  // namespace
+
 void FakeVehicleHardware::storePropInitialValue(const defaultconfig::ConfigDeclaration& config) {
     const VehiclePropConfig& vehiclePropConfig = config.config;
     int propId = vehiclePropConfig.prop;
@@ -75,30 +120,53 @@
         auto result =
                 mServerSidePropStore->writeValue(mValuePool->obtain(prop), /*updateStatus=*/true);
         if (!result.ok()) {
-            ALOGE("failed to write default config value, error: %s",
-                  result.error().message().c_str());
+            ALOGE("failed to write default config value, error: %s, status: %d",
+                  getErrorMsg(result).c_str(), getIntErrorCode(result));
         }
     }
 }
 
-FakeVehicleHardware::FakeVehicleHardware() {
-    mValuePool = std::make_shared<VehiclePropValuePool>();
-    init(mValuePool);
+FakeVehicleHardware::FakeVehicleHardware()
+    : mValuePool(new VehiclePropValuePool),
+      mServerSidePropStore(new VehiclePropertyStore(mValuePool)),
+      mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
+      mFakeUserHal(new FakeUserHal(mValuePool)) {
+    init();
 }
 
 FakeVehicleHardware::FakeVehicleHardware(std::unique_ptr<VehiclePropValuePool> valuePool)
-    : mValuePool(std::move(valuePool)) {
-    init(mValuePool);
+    : mValuePool(std::move(valuePool)),
+      mServerSidePropStore(new VehiclePropertyStore(mValuePool)),
+      mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
+      mFakeUserHal(new FakeUserHal(mValuePool)) {
+    init();
 }
 
-void FakeVehicleHardware::init(std::shared_ptr<VehiclePropValuePool> valuePool) {
-    mServerSidePropStore.reset(new VehiclePropertyStore(valuePool));
+void FakeVehicleHardware::init() {
     for (auto& it : defaultconfig::getDefaultConfigs()) {
         VehiclePropConfig cfg = it.config;
-        mServerSidePropStore->registerProperty(cfg);
+        VehiclePropertyStore::TokenFunction tokenFunction = nullptr;
+
+        if (cfg.prop == OBD2_FREEZE_FRAME) {
+            tokenFunction = [](const VehiclePropValue& propValue) { return propValue.timestamp; };
+        }
+
+        mServerSidePropStore->registerProperty(cfg, tokenFunction);
+        if (obd2frame::FakeObd2Frame::isDiagnosticProperty(cfg)) {
+            // Ignore storing default value for diagnostic property. They have special get/set
+            // logic.
+            continue;
+        }
         storePropInitialValue(it);
     }
 
+    maybeOverrideProperties(VENDOR_OVERRIDE_DIR);
+
+    // OBD2_LIVE_FRAME and OBD2_FREEZE_FRAME must be configured in default configs.
+    mFakeObd2Frame->initObd2LiveFrame(*mServerSidePropStore->getConfig(OBD2_LIVE_FRAME).value());
+    mFakeObd2Frame->initObd2FreezeFrame(
+            *mServerSidePropStore->getConfig(OBD2_FREEZE_FRAME).value());
+
     mServerSidePropStore->setOnValueChangeCallback(
             [this](const VehiclePropValue& value) { return onValueChangeCallback(value); });
 }
@@ -107,27 +175,270 @@
     return mServerSidePropStore->getAllConfigs();
 }
 
+VehiclePropValuePool::RecyclableType FakeVehicleHardware::createApPowerStateReq(
+        VehicleApPowerStateReq state) {
+    auto req = mValuePool->obtain(VehiclePropertyType::INT32_VEC, 2);
+    req->prop = toInt(VehicleProperty::AP_POWER_STATE_REQ);
+    req->areaId = 0;
+    req->timestamp = elapsedRealtimeNano();
+    req->status = VehiclePropertyStatus::AVAILABLE;
+    req->value.int32Values[0] = toInt(state);
+    // Param = 0.
+    req->value.int32Values[1] = 0;
+    return req;
+}
+
+Result<void> FakeVehicleHardware::setApPowerStateReport(const VehiclePropValue& value) {
+    auto updatedValue = mValuePool->obtain(value);
+    updatedValue->timestamp = elapsedRealtimeNano();
+
+    if (auto writeResult = mServerSidePropStore->writeValue(std::move(updatedValue));
+        !writeResult.ok()) {
+        return Error(getIntErrorCode(writeResult))
+               << "failed to write value into property store, error: " << getErrorMsg(writeResult);
+    }
+
+    VehiclePropValuePool::RecyclableType prop;
+    int32_t state = value.value.int32Values[0];
+    switch (state) {
+        case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT):
+            [[fallthrough]];
+        case toInt(VehicleApPowerStateReport::HIBERNATION_EXIT):
+            [[fallthrough]];
+        case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED):
+            [[fallthrough]];
+        case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL):
+            // CPMS is in WAIT_FOR_VHAL state, simply move to ON
+            // Send back to HAL
+            // ALWAYS update status for generated property value
+            prop = createApPowerStateReq(VehicleApPowerStateReq::ON);
+            if (auto writeResult =
+                        mServerSidePropStore->writeValue(std::move(prop), /*updateStatus=*/true);
+                !writeResult.ok()) {
+                return Error(getIntErrorCode(writeResult))
+                       << "failed to write AP_POWER_STATE_REQ into property store, error: "
+                       << getErrorMsg(writeResult);
+            }
+            break;
+        case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY):
+            [[fallthrough]];
+        case toInt(VehicleApPowerStateReport::HIBERNATION_ENTRY):
+            [[fallthrough]];
+        case toInt(VehicleApPowerStateReport::SHUTDOWN_START):
+            // CPMS is in WAIT_FOR_FINISH state, send the FINISHED command
+            // Send back to HAL
+            // ALWAYS update status for generated property value
+            prop = createApPowerStateReq(VehicleApPowerStateReq::FINISHED);
+            if (auto writeResult =
+                        mServerSidePropStore->writeValue(std::move(prop), /*updateStatus=*/true);
+                !writeResult.ok()) {
+                return Error(getIntErrorCode(writeResult))
+                       << "failed to write AP_POWER_STATE_REQ into property store, error: "
+                       << getErrorMsg(writeResult);
+            }
+            break;
+        default:
+            ALOGE("Unknown VehicleApPowerStateReport: %d", state);
+            break;
+    }
+    return {};
+}
+
+bool FakeVehicleHardware::isHvacPropAndHvacNotAvailable(int32_t propId) {
+    std::unordered_set<int32_t> powerProps(std::begin(HVAC_POWER_PROPERTIES),
+                                           std::end(HVAC_POWER_PROPERTIES));
+    if (powerProps.count(propId)) {
+        auto hvacPowerOnResult =
+                mServerSidePropStore->readValue(toInt(VehicleProperty::HVAC_POWER_ON), HVAC_ALL);
+
+        if (hvacPowerOnResult.ok() && hvacPowerOnResult.value()->value.int32Values.size() == 1 &&
+            hvacPowerOnResult.value()->value.int32Values[0] == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+Result<void> FakeVehicleHardware::setUserHalProp(const VehiclePropValue& value) {
+    auto result = mFakeUserHal->onSetProperty(value);
+    if (!result.ok()) {
+        return Error(getIntErrorCode(result))
+               << "onSetProperty(): HAL returned error: " << getErrorMsg(result);
+    }
+    auto& updatedValue = result.value();
+    if (updatedValue != nullptr) {
+        ALOGI("onSetProperty(): updating property returned by HAL: %s",
+              updatedValue->toString().c_str());
+        if (auto writeResult = mServerSidePropStore->writeValue(std::move(result.value()));
+            !writeResult.ok()) {
+            return Error(getIntErrorCode(writeResult))
+                   << "failed to write value into property store, error: "
+                   << getErrorMsg(writeResult);
+        }
+    }
+    return {};
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeVehicleHardware::getUserHalProp(
+        const VehiclePropValue& value) const {
+    auto propId = value.prop;
+    ALOGI("get(): getting value for prop %d from User HAL", propId);
+
+    auto result = mFakeUserHal->onGetProperty(value);
+    if (!result.ok()) {
+        return Error(getIntErrorCode(result))
+               << "get(): User HAL returned error: " << getErrorMsg(result);
+    } else {
+        auto& gotValue = result.value();
+        if (gotValue != nullptr) {
+            ALOGI("get(): User HAL returned value: %s", gotValue->toString().c_str());
+            gotValue->timestamp = elapsedRealtimeNano();
+            return result;
+        } else {
+            return Error(toInt(StatusCode::INTERNAL_ERROR))
+                   << "get(): User HAL returned null value";
+        }
+    }
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeVehicleHardware::maybeGetSpecialValue(
+        const VehiclePropValue& value, bool* isSpecialValue) const {
+    *isSpecialValue = false;
+    int32_t propId = value.prop;
+    Result<VehiclePropValuePool::RecyclableType> result;
+
+    if (mFakeUserHal->isSupported(propId)) {
+        *isSpecialValue = true;
+        return getUserHalProp(value);
+    }
+
+    switch (propId) {
+        case OBD2_FREEZE_FRAME:
+            *isSpecialValue = true;
+            result = mFakeObd2Frame->getObd2FreezeFrame(value);
+            if (result.ok()) {
+                result.value()->timestamp = elapsedRealtimeNano();
+            }
+            return result;
+        case OBD2_FREEZE_FRAME_INFO:
+            *isSpecialValue = true;
+            result = mFakeObd2Frame->getObd2DtcInfo();
+            if (result.ok()) {
+                result.value()->timestamp = elapsedRealtimeNano();
+            }
+            return result;
+        default:
+            // Do nothing.
+            break;
+    }
+
+    return nullptr;
+}
+
+Result<void> FakeVehicleHardware::maybeSetSpecialValue(const VehiclePropValue& value,
+                                                       bool* isSpecialValue) {
+    *isSpecialValue = false;
+    VehiclePropValuePool::RecyclableType updatedValue;
+    int32_t propId = value.prop;
+
+    if (mFakeUserHal->isSupported(propId)) {
+        *isSpecialValue = true;
+        return setUserHalProp(value);
+    }
+
+    if (isHvacPropAndHvacNotAvailable(propId)) {
+        *isSpecialValue = true;
+        return Error(toInt(StatusCode::NOT_AVAILABLE)) << "hvac not available";
+    }
+
+    switch (propId) {
+        case toInt(VehicleProperty::AP_POWER_STATE_REPORT):
+            *isSpecialValue = true;
+            return setApPowerStateReport(value);
+        case toInt(VehicleProperty::VEHICLE_MAP_SERVICE):
+            // Placeholder for future implementation of VMS property in the default hal. For
+            // now, just returns OK; otherwise, hal clients crash with property not supported.
+            *isSpecialValue = true;
+            return {};
+        case OBD2_FREEZE_FRAME_CLEAR:
+            *isSpecialValue = true;
+            return mFakeObd2Frame->clearObd2FreezeFrames(value);
+
+#ifdef ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
+        case toInt(VehicleProperty::CLUSTER_REPORT_STATE):
+            [[fallthrough]];
+        case toInt(VehicleProperty::CLUSTER_REQUEST_DISPLAY):
+            [[fallthrough]];
+        case toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE):
+            [[fallthrough]];
+        case VENDOR_CLUSTER_SWITCH_UI:
+            [[fallthrough]];
+        case VENDOR_CLUSTER_DISPLAY_STATE:
+            *isSpecialValue = true;
+            updatedValue = mValuePool->obtain(getPropType(value.prop));
+            updatedValue->prop = value.prop & ~toInt(VehiclePropertyGroup::MASK);
+            if (getPropGroup(value.prop) == VehiclePropertyGroup::SYSTEM) {
+                updatedValue->prop |= toInt(VehiclePropertyGroup::VENDOR);
+            } else {
+                updatedValue->prop |= toInt(VehiclePropertyGroup::SYSTEM);
+            }
+            updatedValue->value = value.value;
+            updatedValue->timestamp = elapsedRealtimeNano();
+            updatedValue->areaId = value.areaId;
+            if (auto writeResult = mServerSidePropStore->writeValue(std::move(updatedValue));
+                !writeResult.ok()) {
+                return Error(getIntErrorCode(writeResult))
+                       << "failed to write value into property store, error: "
+                       << getErrorMsg(writeResult);
+            }
+            return {};
+#endif  // ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
+
+        default:
+            break;
+    }
+    return {};
+}
+
 StatusCode FakeVehicleHardware::setValues(FakeVehicleHardware::SetValuesCallback&& callback,
                                           const std::vector<SetValueRequest>& requests) {
     std::vector<VehiclePropValue> updatedValues;
     std::vector<SetValueResult> results;
     for (auto& request : requests) {
-        const VehiclePropValue* value = &request.value;
-        ALOGD("setValues(%d)", value->prop);
+        const VehiclePropValue& value = request.value;
+        int propId = value.prop;
 
-        auto updatedValue = mValuePool->obtain(*value);
+        ALOGD("Set value for property ID: %d", propId);
+
+        SetValueResult setValueResult;
+        setValueResult.requestId = request.requestId;
+        setValueResult.status = StatusCode::OK;
+
+        bool isSpecialValue = false;
+        auto setSpecialValueResult = maybeSetSpecialValue(value, &isSpecialValue);
+
+        if (isSpecialValue) {
+            if (!setSpecialValueResult.ok()) {
+                ALOGE("failed to set special value for property ID: %d, error: %s, status: %d",
+                      propId, getErrorMsg(setSpecialValueResult).c_str(),
+                      getIntErrorCode(setSpecialValueResult));
+                setValueResult.status = getErrorCode(setSpecialValueResult);
+            }
+
+            // Special values are already handled.
+            results.push_back(std::move(setValueResult));
+            continue;
+        }
+
+        auto updatedValue = mValuePool->obtain(value);
         int64_t timestamp = elapsedRealtimeNano();
         updatedValue->timestamp = timestamp;
 
         auto writeResult = mServerSidePropStore->writeValue(std::move(updatedValue));
-        SetValueResult setValueResult;
-        setValueResult.requestId = request.requestId;
         if (!writeResult.ok()) {
             ALOGE("failed to write value into property store, error: %s, code: %d",
-                  writeResult.error().message().c_str(), writeResult.error().code());
-            setValueResult.status = StatusCode::INVALID_ARG;
-        } else {
-            setValueResult.status = StatusCode::OK;
+                  getErrorMsg(writeResult).c_str(), getIntErrorCode(writeResult));
+            setValueResult.status = getErrorCode(writeResult);
         }
         results.push_back(std::move(setValueResult));
     }
@@ -143,22 +454,37 @@
                                           const std::vector<GetValueRequest>& requests) const {
     std::vector<GetValueResult> results;
     for (auto& request : requests) {
-        const VehiclePropValue* value = &request.prop;
-        ALOGD("getValues(%d)", value->prop);
+        const VehiclePropValue& value = request.prop;
+        ALOGD("getValues(%d)", value.prop);
 
-        auto readResult = mServerSidePropStore->readValue(*value);
         GetValueResult getValueResult;
         getValueResult.requestId = request.requestId;
-        if (!readResult.ok()) {
-            auto error = readResult.error();
-            if (error.code() == toInt(StatusCode::NOT_AVAILABLE)) {
-                ALOGW("%s", "value has not been set yet");
-                getValueResult.status = StatusCode::NOT_AVAILABLE;
+        bool isSpecialValue = false;
+
+        auto result = maybeGetSpecialValue(value, &isSpecialValue);
+        if (isSpecialValue) {
+            if (!result.ok()) {
+                ALOGE("failed to get special value: %d, error: %s, code: %d", value.prop,
+                      getErrorMsg(result).c_str(), getIntErrorCode(result));
+                getValueResult.status = getErrorCode(result);
             } else {
-                ALOGE("failed to get value, error: %s, code: %d", error.message().c_str(),
-                      error.code());
-                getValueResult.status = StatusCode::INVALID_ARG;
+                getValueResult.status = StatusCode::OK;
+                getValueResult.prop = *result.value();
             }
+            results.push_back(std::move(getValueResult));
+            continue;
+        }
+
+        auto readResult = mServerSidePropStore->readValue(value);
+        if (!readResult.ok()) {
+            StatusCode errorCode = getErrorCode(readResult);
+            if (errorCode == StatusCode::NOT_AVAILABLE) {
+                ALOGW("%s", "value has not been set yet");
+            } else {
+                ALOGE("failed to get value, error: %s, code: %d", getErrorMsg(readResult).c_str(),
+                      toInt(errorCode));
+            }
+            getValueResult.status = errorCode;
         } else {
             getValueResult.status = StatusCode::OK;
             getValueResult.prop = *readResult.value();
@@ -201,6 +527,39 @@
     }
 }
 
+void FakeVehicleHardware::maybeOverrideProperties(const char* overrideDir) {
+    if (android::base::GetBoolProperty(OVERRIDE_PROPERTY, false)) {
+        overrideProperties(overrideDir);
+    }
+}
+
+void FakeVehicleHardware::overrideProperties(const char* overrideDir) {
+    ALOGI("loading vendor override properties from %s", overrideDir);
+    if (auto dir = opendir(overrideDir); dir != NULL) {
+        std::regex regJson(".*[.]json", std::regex::icase);
+        while (auto f = readdir(dir)) {
+            if (!std::regex_match(f->d_name, regJson)) {
+                continue;
+            }
+            std::string file = overrideDir + std::string(f->d_name);
+            JsonFakeValueGenerator tmpGenerator(file);
+
+            std::vector<VehiclePropValue> propValues = tmpGenerator.getAllEvents();
+            for (const VehiclePropValue& prop : propValues) {
+                auto propToStore = mValuePool->obtain(prop);
+                propToStore->timestamp = elapsedRealtimeNano();
+                if (auto result = mServerSidePropStore->writeValue(std::move(propToStore),
+                                                                   /*updateStatus=*/true);
+                    !result.ok()) {
+                    ALOGW("failed to write vendor override properties: %d, error: %s, code: %d",
+                          prop.prop, getErrorMsg(result).c_str(), getIntErrorCode(result));
+                }
+            }
+        }
+        closedir(dir);
+    }
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
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 9f76d09..90d1516 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/Android.bp
@@ -22,16 +22,32 @@
     name: "FakeVehicleHardwareTest",
     vendor: true,
     srcs: ["*.cpp"],
+    cflags: ["-DENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING"],
     header_libs: [
         "IVehicleHardware",
         "VehicleHalDefaultConfig",
+        "VehicleHalTestUtilHeaders",
     ],
     static_libs: [
         "VehicleHalUtils",
         "FakeVehicleHardware",
+        "FakeVehicleHalValueGenerators",
+        "FakeObd2Frame",
+        "FakeUserHal",
         "libgtest",
         "libgmock",
     ],
+    shared_libs: [
+        "libjsoncpp",
+    ],
+    data: [
+        ":FakeVehicleHardwareTestOverrideJson",
+    ],
     defaults: ["VehicleHalDefaults"],
     test_suites: ["device-tests"],
 }
+
+filegroup {
+    name: "FakeVehicleHardwareTestOverrideJson",
+    srcs: ["override/*"],
+}
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 53e647d..88834f3 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -14,18 +14,29 @@
  * limitations under the License.
  */
 
-#include <DefaultConfig.h>
 #include <FakeVehicleHardware.h>
+
+#include <DefaultConfig.h>
+#include <FakeObd2Frame.h>
+#include <FakeUserHal.h>
+#include <PropertyUtils.h>
+#include <TestPropertyUtils.h>
+
+#include <android-base/expected.h>
+#include <android-base/file.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <utils/Log.h>
 #include <utils/SystemClock.h>
 
+#include <inttypes.h>
+#include <vector>
+
 namespace android {
 namespace hardware {
 namespace automotive {
 namespace vehicle {
 namespace fake {
-
 namespace {
 
 using ::aidl::android::hardware::automotive::vehicle::GetValueRequest;
@@ -34,21 +45,42 @@
 using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
 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::VehiclePropConfig;
 using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+using ::android::base::expected;
+using ::android::base::unexpected;
 using ::testing::ContainerEq;
 using ::testing::Eq;
+using ::testing::IsSubsetOf;
 using ::testing::WhenSortedBy;
 
 constexpr int INVALID_PROP_ID = 0;
 
 }  // namespace
 
+// A helper class to access private methods for FakeVehicleHardware.
+class FakeVehicleHardwareTestHelper {
+  public:
+    FakeVehicleHardwareTestHelper(FakeVehicleHardware* hardware) { mHardware = hardware; }
+
+    void overrideProperties(const char* overrideDir) { mHardware->overrideProperties(overrideDir); }
+
+  private:
+    FakeVehicleHardware* mHardware;
+};
+
 class FakeVehicleHardwareTest : public ::testing::Test {
   protected:
-    void SetUp() override {}
+    void SetUp() override {
+        getHardware()->registerOnPropertyChangeEvent(
+                [this](const std::vector<VehiclePropValue>& values) {
+                    return onPropertyChangeEvent(values);
+                });
+    }
 
     FakeVehicleHardware* getHardware() { return &mHardware; }
 
@@ -64,6 +96,63 @@
                 requests);
     }
 
+    StatusCode setValue(const VehiclePropValue& value) {
+        std::vector<SetValueRequest> requests = {
+                SetValueRequest{
+                        .requestId = 0,
+                        .value = value,
+                },
+        };
+
+        if (StatusCode status = setValues(requests); status != StatusCode::OK) {
+            return status;
+        }
+
+        const SetValueResult& result = getSetValueResults().back();
+
+        if (result.requestId != 0) {
+            ALOGE("request ID mismatch, got %" PRId64 ", expect 0", result.requestId);
+            return StatusCode::INTERNAL_ERROR;
+        }
+
+        return result.status;
+    }
+
+    expected<VehiclePropValue, StatusCode> getValue(const VehiclePropValue& value) {
+        std::vector<GetValueRequest> requests = {
+                GetValueRequest{
+                        .requestId = 0,
+                        .prop = value,
+                },
+        };
+
+        if (StatusCode status = getValues(requests); status != StatusCode::OK) {
+            return unexpected(status);
+        }
+
+        const GetValueResult& result = getGetValueResults().back();
+        if (result.requestId != 0) {
+            ALOGE("request ID mismatch, got %" PRId64 ", expect 0", result.requestId);
+            return unexpected(StatusCode::INTERNAL_ERROR);
+        }
+
+        if (result.status != StatusCode::OK) {
+            return unexpected(result.status);
+        }
+
+        if (!result.prop.has_value()) {
+            ALOGE("%s", "result property is empty");
+            return unexpected(StatusCode::INTERNAL_ERROR);
+        }
+
+        return result.prop.value();
+    }
+
+    template <class T>
+    int getStatus(expected<T, StatusCode> result) {
+        return toInt(result.error());
+    }
+
     void onSetValues(const std::vector<SetValueResult> results) {
         for (auto& result : results) {
             mSetValueResults.push_back(result);
@@ -88,6 +177,8 @@
 
     const std::vector<VehiclePropValue>& getChangedProperties() { return mChangedProperties; }
 
+    void clearChangedProperties() { mChangedProperties.clear(); }
+
     static void addSetValueRequest(std::vector<SetValueRequest>& requests,
                                    std::vector<SetValueResult>& expectedResults, int64_t requestId,
                                    const VehiclePropValue& value, StatusCode expectedStatus) {
@@ -168,6 +259,17 @@
     int64_t requestId = 1;
 
     for (auto& config : defaultconfig::getDefaultConfigs()) {
+        if (obd2frame::FakeObd2Frame::isDiagnosticProperty(config.config)) {
+            // Ignore storing default value for diagnostic property. They have special get/set
+            // logic.
+            continue;
+        }
+
+        if (FakeUserHal::isSupported(config.config.prop)) {
+            // Ignore fake user HAL properties, they have special logic for getting values.
+            continue;
+        }
+
         int propId = config.config.prop;
         if (isGlobalProp(propId)) {
             if (config.initialValue == RawPropValues{}) {
@@ -264,6 +366,7 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testRegisterOnPropertyChangeEvent) {
+    // We have already registered this callback in Setup, here we are registering again.
     getHardware()->registerOnPropertyChangeEvent(std::bind(
             &FakeVehicleHardwareTest_testRegisterOnPropertyChangeEvent_Test::onPropertyChangeEvent,
             this, std::placeholders::_1));
@@ -424,6 +527,679 @@
     ASSERT_EQ(getGetValueResults()[1].prop->status, VehiclePropertyStatus::AVAILABLE);
 }
 
+TEST_F(FakeVehicleHardwareTest, testVendorOverrideProperties) {
+    std::string overrideDir = android::base::GetExecutableDirectory() + "/override/";
+    // Set vendor override directory.
+    FakeVehicleHardwareTestHelper helper(getHardware());
+    helper.overrideProperties(overrideDir.c_str());
+
+    // This is the same as the prop in 'gear_selection.json'.
+    int gearProp = toInt(VehicleProperty::GEAR_SELECTION);
+
+    auto result = getValue(VehiclePropValue{
+            .prop = gearProp,
+    });
+
+    ASSERT_TRUE(result.ok()) << "expect to get the overridden property ok: " << getStatus(result);
+    ASSERT_EQ(static_cast<size_t>(1), result.value().value.int32Values.size());
+    ASSERT_EQ(8, result.value().value.int32Values[0]);
+
+    // If we set the value, it should update despite the override.
+    ASSERT_EQ(setValue(VehiclePropValue{
+                      .prop = gearProp,
+                      .value =
+                              {
+                                      .int32Values = {5},
+                              },
+                      .timestamp = elapsedRealtimeNano(),
+              }),
+              StatusCode::OK)
+            << "expect to set the overridden property ok";
+
+    result = getValue(VehiclePropValue{
+            .prop = gearProp,
+    });
+
+    ASSERT_TRUE(result.ok()) << "expect to get the overridden property after setting value ok";
+    ASSERT_EQ(static_cast<size_t>(1), result.value().value.int32Values.size());
+    ASSERT_EQ(5, result.value().value.int32Values[0]);
+}
+
+TEST_F(FakeVehicleHardwareTest, testVendorOverridePropertiesMultipleAreas) {
+    std::string overrideDir = android::base::GetExecutableDirectory() + "/override/";
+    // Set vendor override directory.
+    FakeVehicleHardwareTestHelper helper(getHardware());
+    helper.overrideProperties(overrideDir.c_str());
+
+    // This is the same as the prop in 'hvac_temperature_set.json'.
+    int hvacProp = toInt(VehicleProperty::HVAC_TEMPERATURE_SET);
+
+    auto result = getValue(VehiclePropValue{
+            .prop = hvacProp,
+            .areaId = HVAC_LEFT,
+    });
+
+    ASSERT_TRUE(result.ok()) << "expect to get the overridden property ok: " << getStatus(result);
+    ASSERT_EQ(static_cast<size_t>(1), result.value().value.floatValues.size());
+    ASSERT_EQ(30.0f, result.value().value.floatValues[0]);
+
+    // HVAC_RIGHT should not be affected and return the default value.
+    result = getValue(VehiclePropValue{
+            .prop = hvacProp,
+            .areaId = HVAC_RIGHT,
+    });
+
+    ASSERT_TRUE(result.ok()) << "expect to get the default property ok: " << getStatus(result);
+    ASSERT_EQ(static_cast<size_t>(1), result.value().value.floatValues.size());
+    ASSERT_EQ(20.0f, result.value().value.floatValues[0]);
+}
+
+TEST_F(FakeVehicleHardwareTest, testVendorOverridePropertiesDirDoesNotExist) {
+    // Set vendor override directory to a non-existing dir
+    FakeVehicleHardwareTestHelper helper(getHardware());
+    helper.overrideProperties("123");
+    auto result = getValue(VehiclePropValue{
+            .prop = toInt(VehicleProperty::GEAR_SELECTION),
+    });
+
+    ASSERT_TRUE(result.ok()) << "expect to get the default property ok: " << getStatus(result);
+    ASSERT_EQ(static_cast<size_t>(1), result.value().value.int32Values.size());
+    ASSERT_EQ(4, result.value().value.int32Values[0]);
+}
+
+struct SetSpecialValueTestCase {
+    std::string name;
+    std::vector<VehiclePropValue> valuesToSet;
+    std::vector<VehiclePropValue> expectedValuesToGet;
+};
+
+std::vector<SetSpecialValueTestCase> setSpecialValueTestCases() {
+    return {
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_deep_sleep_exit",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::DEEP_SLEEP_EXIT)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::DEEP_SLEEP_EXIT)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values = {toInt(VehicleApPowerStateReq::ON),
+                                                                  0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_hibernation_exit",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::HIBERNATION_EXIT)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::HIBERNATION_EXIT)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values = {toInt(VehicleApPowerStateReq::ON),
+                                                                  0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_shutdown_cancelled",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::SHUTDOWN_CANCELLED)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::SHUTDOWN_CANCELLED)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values = {toInt(VehicleApPowerStateReq::ON),
+                                                                  0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_wait_for_vhal",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::WAIT_FOR_VHAL)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::WAIT_FOR_VHAL)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values = {toInt(VehicleApPowerStateReq::ON),
+                                                                  0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_deep_sleep_entry",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::DEEP_SLEEP_ENTRY)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::DEEP_SLEEP_ENTRY)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values =
+                                                    {toInt(VehicleApPowerStateReq::FINISHED), 0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_hibernation_entry",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::HIBERNATION_ENTRY)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::HIBERNATION_ENTRY)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values =
+                                                    {toInt(VehicleApPowerStateReq::FINISHED), 0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "set_ap_power_state_report_shutdown_start",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::SHUTDOWN_START)},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT),
+                                            .value.int32Values = {toInt(
+                                                    VehicleApPowerStateReport::SHUTDOWN_START)},
+                                    },
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+                                            .status = VehiclePropertyStatus::AVAILABLE,
+                                            .value.int32Values =
+                                                    {toInt(VehicleApPowerStateReq::FINISHED), 0},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "cluster_report_state_to_vendor",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::CLUSTER_REPORT_STATE),
+                                            .value.int32Values = {1},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = VENDOR_CLUSTER_REPORT_STATE,
+                                            .value.int32Values = {1},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "cluster_request_display_to_vendor",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::CLUSTER_REQUEST_DISPLAY),
+                                            .value.int32Values = {1},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = VENDOR_CLUSTER_REQUEST_DISPLAY,
+                                            .value.int32Values = {1},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "cluster_navigation_state_to_vendor",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(
+                                                    VehicleProperty::CLUSTER_NAVIGATION_STATE),
+                                            .value.byteValues = {0x1},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = VENDOR_CLUSTER_NAVIGATION_STATE,
+                                            .value.byteValues = {0x1},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "vendor_cluster_switch_ui_to_system",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = VENDOR_CLUSTER_SWITCH_UI,
+                                            .value.int32Values = {1},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::CLUSTER_SWITCH_UI),
+                                            .value.int32Values = {1},
+                                    },
+                            },
+            },
+            SetSpecialValueTestCase{
+                    .name = "vendor_cluster_display_state_to_system",
+                    .valuesToSet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = VENDOR_CLUSTER_DISPLAY_STATE,
+                                            .value.int32Values = {1, 2},
+                                    },
+                            },
+                    .expectedValuesToGet =
+                            {
+                                    VehiclePropValue{
+                                            .prop = toInt(VehicleProperty::CLUSTER_DISPLAY_STATE),
+                                            .value.int32Values = {1, 2},
+                                    },
+                            },
+            },
+    };
+}
+
+class FakeVehicleHardwareSpecialValuesTest
+    : public FakeVehicleHardwareTest,
+      public testing::WithParamInterface<SetSpecialValueTestCase> {};
+
+TEST_P(FakeVehicleHardwareSpecialValuesTest, testSetSpecialProperties) {
+    const SetSpecialValueTestCase& tc = GetParam();
+
+    for (const auto& value : tc.valuesToSet) {
+        ASSERT_EQ(setValue(value), StatusCode::OK) << "failed to set property " << value.prop;
+    }
+
+    std::vector<VehiclePropValue> gotValues;
+
+    for (const auto& value : tc.expectedValuesToGet) {
+        auto result = getValue(VehiclePropValue{.prop = value.prop});
+
+        ASSERT_TRUE(result.ok()) << "failed to get property " << value.prop
+                                 << " status:" << getStatus(result);
+
+        gotValues.push_back(result.value());
+        VehiclePropValue valueWithNoTimestamp = result.value();
+        valueWithNoTimestamp.timestamp = 0;
+
+        ASSERT_EQ(valueWithNoTimestamp, value);
+    }
+
+    // Some of the updated properties might be the same as default config, thus not causing
+    // a property change event. So the changed properties should be a subset of all the updated
+    // properties.
+    ASSERT_THAT(getChangedProperties(), WhenSortedBy(mPropValueCmp, IsSubsetOf(gotValues)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SpecialValuesTests, FakeVehicleHardwareSpecialValuesTest,
+        testing::ValuesIn(setSpecialValueTestCases()),
+        [](const testing::TestParamInfo<FakeVehicleHardwareSpecialValuesTest::ParamType>& info) {
+            return info.param.name;
+        });
+
+TEST_F(FakeVehicleHardwareTest, testGetObd2FreezeFrame) {
+    int64_t timestamp = elapsedRealtimeNano();
+
+    auto result = getValue(VehiclePropValue{.prop = OBD2_FREEZE_FRAME_INFO});
+
+    ASSERT_TRUE(result.ok());
+
+    auto propValue = result.value();
+    ASSERT_GE(propValue.timestamp, timestamp);
+    ASSERT_EQ(propValue.value.int64Values.size(), static_cast<size_t>(3))
+            << "expect 3 obd2 freeze frames stored";
+
+    for (int64_t timestamp : propValue.value.int64Values) {
+        auto freezeFrameResult = getValue(VehiclePropValue{
+                .prop = OBD2_FREEZE_FRAME,
+                .value.int64Values = {timestamp},
+        });
+
+        EXPECT_TRUE(result.ok()) << "expect to get freeze frame for timestamp " << timestamp
+                                 << " ok";
+        EXPECT_GE(freezeFrameResult.value().timestamp, timestamp);
+    }
+}
+
+TEST_F(FakeVehicleHardwareTest, testClearObd2FreezeFrame) {
+    int64_t timestamp = elapsedRealtimeNano();
+
+    auto getValueResult = getValue(VehiclePropValue{.prop = OBD2_FREEZE_FRAME_INFO});
+
+    ASSERT_TRUE(getValueResult.ok());
+
+    auto propValue = getValueResult.value();
+    ASSERT_GE(propValue.timestamp, timestamp);
+    ASSERT_EQ(propValue.value.int64Values.size(), static_cast<size_t>(3))
+            << "expect 3 obd2 freeze frames stored";
+
+    // No int64Values should clear all freeze frames.
+    StatusCode status = setValue(VehiclePropValue{.prop = OBD2_FREEZE_FRAME_CLEAR});
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    getValueResult = getValue(VehiclePropValue{.prop = OBD2_FREEZE_FRAME_INFO});
+
+    ASSERT_TRUE(getValueResult.ok());
+    ASSERT_EQ(getValueResult.value().value.int64Values.size(), static_cast<size_t>(0))
+            << "expect 0 obd2 freeze frames after cleared";
+}
+
+TEST_F(FakeVehicleHardwareTest, testSetVehicleMapService) {
+    StatusCode status =
+            setValue(VehiclePropValue{.prop = toInt(VehicleProperty::VEHICLE_MAP_SERVICE)});
+
+    EXPECT_EQ(status, StatusCode::OK);
+
+    auto getValueResult =
+            getValue(VehiclePropValue{.prop = toInt(VehicleProperty::VEHICLE_MAP_SERVICE)});
+
+    EXPECT_FALSE(getValueResult.ok());
+    EXPECT_EQ(getValueResult.error(), StatusCode::NOT_AVAILABLE);
+}
+
+TEST_F(FakeVehicleHardwareTest, testGetUserPropertySetOnly) {
+    for (VehicleProperty prop : std::vector<VehicleProperty>({
+                 VehicleProperty::INITIAL_USER_INFO,
+                 VehicleProperty::SWITCH_USER,
+                 VehicleProperty::CREATE_USER,
+                 VehicleProperty::REMOVE_USER,
+         })) {
+        auto result = getValue(VehiclePropValue{.prop = toInt(prop)});
+
+        EXPECT_FALSE(result.ok());
+        if (!result.ok()) {
+            EXPECT_EQ(result.error(), StatusCode::INVALID_ARG);
+        }
+    }
+}
+
+TEST_F(FakeVehicleHardwareTest, testGetUserIdAssoc) {
+    int32_t userIdAssocProp = toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION);
+
+    auto result = getValue(VehiclePropValue{.prop = userIdAssocProp});
+
+    // Default returns NOT_AVAILABLE.
+    ASSERT_FALSE(result.ok());
+    ASSERT_EQ(result.error(), StatusCode::NOT_AVAILABLE);
+
+    // This is the same example as used in User HAL Emulation doc.
+    VehiclePropValue valueToSet = {
+            .prop = toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION),
+            .areaId = 1,
+            .value.int32Values = {666, 1, 1, 2},
+    };
+
+    StatusCode status = setValue(valueToSet);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    result = getValue(VehiclePropValue{
+            .prop = userIdAssocProp,
+            // Request ID
+            .value.int32Values = {1},
+    });
+
+    ASSERT_TRUE(result.ok());
+
+    auto& gotValue = result.value();
+    gotValue.timestamp = 0;
+
+    // Expect to get the same request ID.
+    valueToSet.value.int32Values[0] = 1;
+
+    ASSERT_EQ(gotValue, valueToSet);
+}
+
+TEST_F(FakeVehicleHardwareTest, testSwitchUser) {
+    // This is the same example as used in User HAL Emulation doc.
+    VehiclePropValue valueToSet = {
+            .prop = toInt(VehicleProperty::SWITCH_USER),
+            .areaId = 1,
+            .value.int32Values = {666, 3, 2},
+    };
+
+    StatusCode status = setValue(valueToSet);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Simulate a request from Android side.
+    VehiclePropValue switchUserRequest = {
+            .prop = toInt(VehicleProperty::SWITCH_USER),
+            .areaId = 0,
+            .value.int32Values = {666, 3},
+    };
+    // Clear existing events.
+    clearChangedProperties();
+
+    status = setValue(switchUserRequest);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Should generate an event for user hal response.
+    auto events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+
+    events[0].timestamp = 0;
+    ASSERT_EQ(events[0], valueToSet);
+
+    // Try to get switch_user again, should return default value.
+    clearChangedProperties();
+    status = setValue(switchUserRequest);
+    ASSERT_EQ(status, StatusCode::OK);
+
+    events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+    events[0].timestamp = 0;
+    ASSERT_EQ(events[0], (VehiclePropValue{
+                                 .areaId = 0,
+                                 .prop = toInt(VehicleProperty::SWITCH_USER),
+                                 .value.int32Values =
+                                         {
+                                                 // Request ID
+                                                 666,
+                                                 // VEHICLE_RESPONSE
+                                                 3,
+                                                 // SUCCESS
+                                                 1,
+                                         },
+                         }));
+}
+
+TEST_F(FakeVehicleHardwareTest, testCreateUser) {
+    // This is the same example as used in User HAL Emulation doc.
+    VehiclePropValue valueToSet = {
+            .prop = toInt(VehicleProperty::CREATE_USER),
+            .areaId = 1,
+            .value.int32Values = {666, 2},
+    };
+
+    StatusCode status = setValue(valueToSet);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Simulate a request from Android side.
+    VehiclePropValue createUserRequest = {
+            .prop = toInt(VehicleProperty::CREATE_USER),
+            .areaId = 0,
+            .value.int32Values = {666},
+    };
+    // Clear existing events.
+    clearChangedProperties();
+
+    status = setValue(createUserRequest);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Should generate an event for user hal response.
+    auto events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+    events[0].timestamp = 0;
+    EXPECT_EQ(events[0], valueToSet);
+
+    // Try to get create_user again, should return default value.
+    clearChangedProperties();
+    status = setValue(createUserRequest);
+    ASSERT_EQ(status, StatusCode::OK);
+
+    events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+    events[0].timestamp = 0;
+    ASSERT_EQ(events[0], (VehiclePropValue{
+                                 .areaId = 0,
+                                 .prop = toInt(VehicleProperty::CREATE_USER),
+                                 .value.int32Values =
+                                         {
+                                                 // Request ID
+                                                 666,
+                                                 // SUCCESS
+                                                 1,
+                                         },
+                         }));
+}
+
+TEST_F(FakeVehicleHardwareTest, testInitialUserInfo) {
+    // This is the same example as used in User HAL Emulation doc.
+    VehiclePropValue valueToSet = {
+            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .areaId = 1,
+            .value.int32Values = {666, 1, 11},
+    };
+
+    StatusCode status = setValue(valueToSet);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Simulate a request from Android side.
+    VehiclePropValue initialUserInfoRequest = {
+            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .areaId = 0,
+            .value.int32Values = {3},
+    };
+    // Clear existing events.
+    clearChangedProperties();
+
+    status = setValue(initialUserInfoRequest);
+
+    ASSERT_EQ(status, StatusCode::OK);
+
+    // Should generate an event for user hal response.
+    auto events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+    events[0].timestamp = 0;
+    EXPECT_EQ(events[0], (VehiclePropValue{
+                                 .areaId = 1,
+                                 .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+                                 .value.int32Values = {3, 1, 11},
+                         }));
+
+    // Try to get create_user again, should return default value.
+    clearChangedProperties();
+    status = setValue(initialUserInfoRequest);
+    ASSERT_EQ(status, StatusCode::OK);
+
+    events = getChangedProperties();
+    ASSERT_EQ(events.size(), static_cast<size_t>(1));
+    events[0].timestamp = 0;
+    EXPECT_EQ(events[0], (VehiclePropValue{
+                                 .areaId = 0,
+                                 .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+                                 .value.int32Values =
+                                         {
+                                                 // Request ID
+                                                 3,
+                                                 // ACTION: DEFAULT
+                                                 0,
+                                                 // User id: 0
+                                                 0,
+                                                 // Flags: 0
+                                                 0,
+                                         },
+                                 .value.stringValue = "||",
+                         }));
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/gear_selection.json b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/gear_selection.json
new file mode 100644
index 0000000..59666b8
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/gear_selection.json
@@ -0,0 +1,9 @@
+[
+  {
+    "timestamp": 1000000,
+    "areaId": 0,
+    "value": 8,
+    // GEAR_SELECTION
+    "prop": 289408000
+  }
+]
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/hvac_temperature_set.json b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/hvac_temperature_set.json
new file mode 100644
index 0000000..93a97ed
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/override/hvac_temperature_set.json
@@ -0,0 +1,10 @@
+[
+  {
+    "timestamp": 1000000,
+    // HVAC_LEFT
+    "areaId": 49,
+    "value": 30,
+    // HVAC_TEMPERATURE_SET
+    "prop": 358614275
+  }
+]
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/Android.bp
new file mode 100644
index 0000000..c1cee84
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/Android.bp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "FakeObd2Frame",
+    vendor: true,
+    srcs: ["src/*.cpp"],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    defaults: ["VehicleHalDefaults"],
+    static_libs: [
+        "VehicleHalUtils",
+    ],
+    export_static_lib_headers: ["VehicleHalUtils"],
+}
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/FakeObd2Frame.h b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/FakeObd2Frame.h
new file mode 100644
index 0000000..118bb34
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/FakeObd2Frame.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_FakeObd2Frame_H_
+#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_FakeObd2Frame_H_
+
+#include <Obd2SensorStore.h>
+#include <VehicleHalTypes.h>
+#include <VehiclePropertyStore.h>
+
+#include <android-base/result.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+class FakeObd2Frame final {
+  public:
+    explicit FakeObd2Frame(std::shared_ptr<VehiclePropertyStore> propStore)
+        : mPropStore(propStore) {}
+
+    void initObd2LiveFrame(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& propConfig);
+    void initObd2FreezeFrame(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& propConfig);
+    ::android::base::Result<VehiclePropValuePool::RecyclableType> getObd2FreezeFrame(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue&
+                    requestedPropValue) const;
+    ::android::base::Result<VehiclePropValuePool::RecyclableType> getObd2DtcInfo() const;
+    ::android::base::Result<void> clearObd2FreezeFrames(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+    static bool isDiagnosticProperty(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& propConfig);
+
+  private:
+    std::shared_ptr<VehiclePropertyStore> mPropStore;
+
+    std::unique_ptr<Obd2SensorStore> fillDefaultObd2Frame(size_t numVendorIntegerSensors,
+                                                          size_t numVendorFloatSensors);
+};
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_FakeObd2Frame_H_
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/Obd2SensorStore.h b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/Obd2SensorStore.h
new file mode 100644
index 0000000..f6075cb
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/include/Obd2SensorStore.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_Obd2SensorStore_H_
+#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_Obd2SensorStore_H_
+
+#include <VehicleHalTypes.h>
+#include <VehicleObjectPool.h>
+#include <VehicleUtils.h>
+
+#include <android-base/result.h>
+
+#include <memory>
+#include <vector>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+// This class wraps all the logic required to create an OBD2 frame.
+// It allows storing sensor values, setting appropriate bitmasks as needed, and returning
+// appropriately laid out storage of sensor values suitable for being returned via a VehicleHal
+// implementation.
+class Obd2SensorStore final {
+  public:
+    // Creates a sensor storage with a given number of vendor-specific sensors.
+    Obd2SensorStore(std::shared_ptr<VehiclePropValuePool> valuePool, size_t numVendorIntegerSensors,
+                    size_t numVendorFloatSensors);
+
+    template <class T>
+    static int getLastIndex() {
+        auto range = ::ndk::enum_range<T>();
+        auto it = range.begin();
+        while (std::next(it) != range.end()) {
+            it++;
+        }
+        return toInt(*it);
+    }
+
+    // Stores an integer-valued sensor.
+    ::aidl::android::hardware::automotive::vehicle::StatusCode setIntegerSensor(
+            ::aidl::android::hardware::automotive::vehicle::DiagnosticIntegerSensorIndex index,
+            int32_t value);
+    // Stores an integer-valued sensor.
+    ::aidl::android::hardware::automotive::vehicle::StatusCode setIntegerSensor(size_t index,
+                                                                                int32_t value);
+    // Stores a float-valued sensor.
+    ::aidl::android::hardware::automotive::vehicle::StatusCode setFloatSensor(
+            ::aidl::android::hardware::automotive::vehicle::DiagnosticFloatSensorIndex index,
+            float value);
+    // Stores a float-valued sensor.
+    ::aidl::android::hardware::automotive::vehicle::StatusCode setFloatSensor(size_t index,
+                                                                              float value);
+
+    // Returns a sensor property value using the given DTC.
+    VehiclePropValuePool::RecyclableType getSensorProperty(const std::string& dtc) const;
+
+  private:
+    class BitmaskInVector final {
+      public:
+        explicit BitmaskInVector(size_t numBits = 0);
+        void resize(size_t numBits);
+        ::android::base::Result<bool> get(size_t index) const;
+        ::android::base::Result<void> set(size_t index, bool value);
+
+        const std::vector<uint8_t>& getBitmask() const;
+
+      private:
+        std::vector<uint8_t> mStorage;
+        size_t mNumBits;
+    };
+
+    std::vector<int32_t> mIntegerSensors;
+    std::vector<float> mFloatSensors;
+    BitmaskInVector mSensorsBitmask;
+    std::shared_ptr<VehiclePropValuePool> mValuePool;
+
+    // Returns a vector that contains all integer sensors stored.
+    const std::vector<int32_t>& getIntegerSensors() const;
+    // Returns a vector that contains all float sensors stored.
+    const std::vector<float>& getFloatSensors() const;
+    // Returns a vector that contains a bitmask for all stored sensors.
+    const std::vector<uint8_t>& getSensorsBitmask() const;
+};
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // android_hardware_automotive_vehicle_aidl_impl_fake_impl_obd2frame_include_Obd2SensorStore_H_
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/FakeObd2Frame.cpp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/FakeObd2Frame.cpp
new file mode 100644
index 0000000..5585fb4
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/FakeObd2Frame.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 "FakeObd2Frame.h"
+#include "Obd2SensorStore.h"
+
+#include <PropertyUtils.h>
+#include <VehicleHalTypes.h>
+#include <VehiclePropertyStore.h>
+#include <VehicleUtils.h>
+
+#include <android-base/result.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticFloatSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticIntegerSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::Obd2CommonIgnitionMonitors;
+using ::aidl::android::hardware::automotive::vehicle::Obd2FuelSystemStatus;
+using ::aidl::android::hardware::automotive::vehicle::Obd2FuelType;
+using ::aidl::android::hardware::automotive::vehicle::Obd2IgnitionMonitorKind;
+using ::aidl::android::hardware::automotive::vehicle::Obd2SecondaryAirStatus;
+using ::aidl::android::hardware::automotive::vehicle::Obd2SparkIgnitionMonitors;
+using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+using ::android::base::Error;
+using ::android::base::Result;
+
+std::unique_ptr<Obd2SensorStore> FakeObd2Frame::fillDefaultObd2Frame(size_t numVendorIntegerSensors,
+                                                                     size_t numVendorFloatSensors) {
+    std::unique_ptr<Obd2SensorStore> sensorStore(new Obd2SensorStore(
+            mPropStore->getValuePool(), numVendorIntegerSensors, numVendorFloatSensors));
+
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::FUEL_SYSTEM_STATUS,
+                                  toInt(Obd2FuelSystemStatus::CLOSED_LOOP));
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::MALFUNCTION_INDICATOR_LIGHT_ON, 0);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::IGNITION_MONITORS_SUPPORTED,
+                                  toInt(Obd2IgnitionMonitorKind::SPARK));
+    sensorStore->setIntegerSensor(
+            DiagnosticIntegerSensorIndex::IGNITION_SPECIFIC_MONITORS,
+            toInt(Obd2CommonIgnitionMonitors::COMPONENTS_AVAILABLE) |
+                    toInt(Obd2CommonIgnitionMonitors::MISFIRE_AVAILABLE) |
+                    toInt(Obd2SparkIgnitionMonitors::AC_REFRIGERANT_AVAILABLE) |
+                    toInt(Obd2SparkIgnitionMonitors::EVAPORATIVE_SYSTEM_AVAILABLE));
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::INTAKE_AIR_TEMPERATURE, 35);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::COMMANDED_SECONDARY_AIR_STATUS,
+                                  toInt(Obd2SecondaryAirStatus::FROM_OUTSIDE_OR_OFF));
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::NUM_OXYGEN_SENSORS_PRESENT, 1);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::RUNTIME_SINCE_ENGINE_START, 500);
+    sensorStore->setIntegerSensor(
+            DiagnosticIntegerSensorIndex::DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON, 0);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::WARMUPS_SINCE_CODES_CLEARED, 51);
+    sensorStore->setIntegerSensor(
+            DiagnosticIntegerSensorIndex::DISTANCE_TRAVELED_SINCE_CODES_CLEARED, 365);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::ABSOLUTE_BAROMETRIC_PRESSURE, 30);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::CONTROL_MODULE_VOLTAGE, 12);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::AMBIENT_AIR_TEMPERATURE, 18);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::MAX_FUEL_AIR_EQUIVALENCE_RATIO, 1);
+    sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::FUEL_TYPE,
+                                  toInt(Obd2FuelType::GASOLINE));
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::CALCULATED_ENGINE_LOAD, 0.153);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::SHORT_TERM_FUEL_TRIM_BANK1, -0.16);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::LONG_TERM_FUEL_TRIM_BANK1, -0.16);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::SHORT_TERM_FUEL_TRIM_BANK2, -0.16);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::LONG_TERM_FUEL_TRIM_BANK2, -0.16);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::INTAKE_MANIFOLD_ABSOLUTE_PRESSURE, 7.5);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ENGINE_RPM, 1250.);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::VEHICLE_SPEED, 40.);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::TIMING_ADVANCE, 2.5);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::THROTTLE_POSITION, 19.75);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::OXYGEN_SENSOR1_VOLTAGE, 0.265);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::FUEL_TANK_LEVEL_INPUT, 0.824);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::EVAPORATION_SYSTEM_VAPOR_PRESSURE,
+                                -0.373);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::CATALYST_TEMPERATURE_BANK1_SENSOR1,
+                                190.);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::RELATIVE_THROTTLE_POSITION, 3.);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ABSOLUTE_THROTTLE_POSITION_B, 0.306);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ACCELERATOR_PEDAL_POSITION_D, 0.188);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ACCELERATOR_PEDAL_POSITION_E, 0.094);
+    sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::COMMANDED_THROTTLE_ACTUATOR, 0.024);
+
+    return sensorStore;
+}
+
+void FakeObd2Frame::initObd2LiveFrame(const VehiclePropConfig& propConfig) {
+    auto sensorStore = fillDefaultObd2Frame(static_cast<size_t>(propConfig.configArray[0]),
+                                            static_cast<size_t>(propConfig.configArray[1]));
+    auto liveObd2Frame = sensorStore->getSensorProperty("");
+    liveObd2Frame->prop = OBD2_LIVE_FRAME;
+
+    mPropStore->writeValue(std::move(liveObd2Frame), /*updateStatus=*/true);
+}
+
+void FakeObd2Frame::initObd2FreezeFrame(const VehiclePropConfig& propConfig) {
+    auto sensorStore = fillDefaultObd2Frame(static_cast<size_t>(propConfig.configArray[0]),
+                                            static_cast<size_t>(propConfig.configArray[1]));
+
+    static std::vector<std::string> sampleDtcs = {"P0070", "P0102", "P0123"};
+    for (auto&& dtc : sampleDtcs) {
+        auto freezeFrame = sensorStore->getSensorProperty(dtc);
+        freezeFrame->prop = OBD2_FREEZE_FRAME;
+
+        mPropStore->writeValue(std::move(freezeFrame), /*updateStatus=*/true);
+    }
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeObd2Frame::getObd2FreezeFrame(
+        const VehiclePropValue& requestedPropValue) const {
+    if (requestedPropValue.value.int64Values.size() != 1) {
+        return Error(toInt(StatusCode::INVALID_ARG))
+               << "asked for OBD2_FREEZE_FRAME without valid timestamp";
+    }
+    auto readValuesResult = mPropStore->readValuesForProperty(OBD2_FREEZE_FRAME);
+    if (!readValuesResult.ok()) {
+        return Error(toInt(StatusCode::INTERNAL_ERROR))
+               << "failed to read OBD2_FREEZE_FRAME property: "
+               << readValuesResult.error().message();
+    }
+    if (readValuesResult.value().size() == 0) {
+        // Should no freeze frame be available at the given timestamp, a response of NOT_AVAILABLE
+        // must be returned by the implementation
+        return Error(toInt(StatusCode::NOT_AVAILABLE));
+    }
+    auto timestamp = requestedPropValue.value.int64Values[0];
+    auto readValueResult = mPropStore->readValue(OBD2_FREEZE_FRAME, /*area=*/0, timestamp);
+    if (!readValueResult.ok()) {
+        return Error(toInt(StatusCode::INVALID_ARG))
+               << "asked for OBD2_FREEZE_FRAME at invalid timestamp";
+    }
+    return readValueResult;
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeObd2Frame::getObd2DtcInfo() const {
+    std::vector<int64_t> timestamps;
+    auto result = mPropStore->readValuesForProperty(OBD2_FREEZE_FRAME);
+    if (!result.ok()) {
+        return Error(toInt(StatusCode::INTERNAL_ERROR))
+               << "failed to read OBD2_FREEZE_FRAME property: " << result.error().message();
+    }
+    for (const auto& freezeFrame : result.value()) {
+        timestamps.push_back(freezeFrame->timestamp);
+    }
+    auto outValue =
+            mPropStore->getValuePool()->obtain(VehiclePropertyType::INT64_VEC, timestamps.size());
+    outValue->value.int64Values = timestamps;
+    outValue->prop = OBD2_FREEZE_FRAME_INFO;
+    return outValue;
+}
+
+Result<void> FakeObd2Frame::clearObd2FreezeFrames(const VehiclePropValue& propValue) {
+    if (propValue.value.int64Values.size() == 0) {
+        mPropStore->removeValuesForProperty(OBD2_FREEZE_FRAME);
+        return {};
+    }
+    for (int64_t timestamp : propValue.value.int64Values) {
+        auto result = mPropStore->readValue(OBD2_FREEZE_FRAME, 0, timestamp);
+        if (!result.ok()) {
+            return Error(toInt(StatusCode::INVALID_ARG))
+                   << "asked for OBD2_FREEZE_FRAME at invalid timestamp, error: %s"
+                   << result.error().message();
+        }
+        mPropStore->removeValue(*result.value());
+    }
+    return {};
+}
+
+bool FakeObd2Frame::isDiagnosticProperty(const VehiclePropConfig& propConfig) {
+    return (propConfig.prop == OBD2_LIVE_FRAME || propConfig.prop == OBD2_FREEZE_FRAME ||
+            propConfig.prop == OBD2_FREEZE_FRAME_CLEAR ||
+            propConfig.prop == OBD2_FREEZE_FRAME_INFO);
+}
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/Obd2SensorStore.cpp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/Obd2SensorStore.cpp
new file mode 100644
index 0000000..4050614
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/src/Obd2SensorStore.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 "Obd2SensorStore.h"
+
+#include <VehicleUtils.h>
+
+#include <utils/SystemClock.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticFloatSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticIntegerSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
+using ::android::base::Error;
+using ::android::base::Result;
+
+Obd2SensorStore::BitmaskInVector::BitmaskInVector(size_t numBits) {
+    mNumBits = numBits;
+    resize(numBits);
+}
+
+void Obd2SensorStore::BitmaskInVector::resize(size_t numBits) {
+    mNumBits = numBits;
+    mStorage = std::vector<uint8_t>((numBits + 7) / 8, 0);
+}
+
+Result<void> Obd2SensorStore::BitmaskInVector::set(size_t index, bool value) {
+    const size_t byteIndex = index / 8;
+    const size_t bitIndex = index % 8;
+    if (index >= mNumBits) {
+        return Error() << "out of bound";
+    }
+    const uint8_t byte = mStorage[byteIndex];
+    uint8_t newValue = value ? (byte | (1 << bitIndex)) : (byte & ~(1 << bitIndex));
+    mStorage[byteIndex] = newValue;
+    return {};
+}
+
+Result<bool> Obd2SensorStore::BitmaskInVector::get(size_t index) const {
+    const size_t byteIndex = index / 8;
+    const size_t bitIndex = index % 8;
+    if (index >= mNumBits) {
+        return Error() << "out of bound";
+    }
+    const uint8_t byte = mStorage[byteIndex];
+    return (byte & (1 << bitIndex)) != 0;
+}
+
+const std::vector<uint8_t>& Obd2SensorStore::BitmaskInVector::getBitmask() const {
+    return mStorage;
+}
+
+Obd2SensorStore::Obd2SensorStore(std::shared_ptr<VehiclePropValuePool> valuePool,
+                                 size_t numVendorIntegerSensors, size_t numVendorFloatSensors)
+    : mValuePool(valuePool) {
+    const size_t numSystemIntegerSensors = getLastIndex<DiagnosticIntegerSensorIndex>() + 1;
+    const size_t numSystemFloatSensors = getLastIndex<DiagnosticFloatSensorIndex>() + 1;
+    mIntegerSensors = std::vector<int32_t>(numSystemIntegerSensors + numVendorIntegerSensors, 0);
+    mFloatSensors = std::vector<float>(numSystemFloatSensors + numVendorFloatSensors, 0);
+    mSensorsBitmask.resize(mIntegerSensors.size() + mFloatSensors.size());
+}
+
+StatusCode Obd2SensorStore::setIntegerSensor(DiagnosticIntegerSensorIndex index, int32_t value) {
+    return setIntegerSensor(toInt(index), value);
+}
+StatusCode Obd2SensorStore::setFloatSensor(DiagnosticFloatSensorIndex index, float value) {
+    return setFloatSensor(toInt(index), value);
+}
+
+StatusCode Obd2SensorStore::setIntegerSensor(size_t index, int32_t value) {
+    if (index >= mIntegerSensors.size()) {
+        ALOGE("failed to set integer sensor: OOB");
+        return StatusCode::INVALID_ARG;
+    }
+    mIntegerSensors[index] = value;
+    if (auto result = mSensorsBitmask.set(index, true); !result.ok()) {
+        ALOGE("failed to set integer sensor: %s", result.error().message().c_str());
+        return StatusCode::INVALID_ARG;
+    }
+    return StatusCode::OK;
+}
+
+StatusCode Obd2SensorStore::setFloatSensor(size_t index, float value) {
+    if (index >= mFloatSensors.size()) {
+        ALOGE("failed to set integer sensor: OOB");
+        return StatusCode::INVALID_ARG;
+    }
+    mFloatSensors[index] = value;
+    if (auto result = mSensorsBitmask.set(index + mIntegerSensors.size(), true); !result.ok()) {
+        ALOGE("failed to set float sensor: %s", result.error().message().c_str());
+        return StatusCode::INVALID_ARG;
+    }
+    return StatusCode::OK;
+}
+
+const std::vector<int32_t>& Obd2SensorStore::getIntegerSensors() const {
+    return mIntegerSensors;
+}
+
+const std::vector<float>& Obd2SensorStore::getFloatSensors() const {
+    return mFloatSensors;
+}
+
+const std::vector<uint8_t>& Obd2SensorStore::getSensorsBitmask() const {
+    return mSensorsBitmask.getBitmask();
+}
+
+VehiclePropValuePool::RecyclableType Obd2SensorStore::getSensorProperty(
+        const std::string& dtc) const {
+    auto propValue = mValuePool->obtain(VehiclePropertyType::MIXED);
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values = getIntegerSensors();
+    propValue->value.floatValues = getFloatSensors();
+    propValue->value.byteValues = getSensorsBitmask();
+    propValue->value.stringValue = dtc;
+    return propValue;
+}
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Android.bp
new file mode 100644
index 0000000..55b8c93
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "FakeObd2FrameTest",
+    vendor: true,
+    srcs: ["*.cpp"],
+    defaults: ["VehicleHalDefaults"],
+    static_libs: [
+        "FakeObd2Frame",
+        "VehicleHalUtils",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/FakeObd2FrameTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/FakeObd2FrameTest.cpp
new file mode 100644
index 0000000..54ec1b2
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/FakeObd2FrameTest.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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 "FakeObd2Frame.h"
+
+#include <PropertyUtils.h>
+#include <VehicleObjectPool.h>
+#include <VehiclePropertyStore.h>
+#include <gtest/gtest.h>
+#include <utils/SystemClock.h>
+
+#include <set>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+
+class FakeObd2FrameTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        std::shared_ptr<VehiclePropValuePool> valuePool = std::make_shared<VehiclePropValuePool>();
+        mPropertyStore = std::make_shared<VehiclePropertyStore>(valuePool);
+        mObd2Frame = std::make_unique<FakeObd2Frame>(mPropertyStore);
+
+        mPropertyStore->registerProperty(getObd2LiveFrameConfig());
+        mPropertyStore->registerProperty(
+                getObd2FreezeFrameConfig(),
+                [](const VehiclePropValue& propValue) { return propValue.timestamp; });
+        mPropertyStore->registerProperty(getObd2FreezeFrameInfoConfig());
+    }
+
+    VehiclePropConfig getObd2LiveFrameConfig() {
+        return VehiclePropConfig{.prop = OBD2_LIVE_FRAME,
+                                 .access = VehiclePropertyAccess::READ,
+                                 .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+                                 .configArray = {1, 1}};
+    }
+
+    VehiclePropConfig getObd2FreezeFrameConfig() {
+        return VehiclePropConfig{.prop = OBD2_FREEZE_FRAME,
+                                 .access = VehiclePropertyAccess::READ,
+                                 .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+                                 .configArray = {0, 0}};
+    }
+
+    VehiclePropConfig getObd2FreezeFrameInfoConfig() {
+        return VehiclePropConfig{.prop = OBD2_FREEZE_FRAME_INFO,
+                                 .access = VehiclePropertyAccess::READ,
+                                 .changeMode = VehiclePropertyChangeMode::ON_CHANGE};
+    }
+
+    FakeObd2Frame* getFakeObd2Frame() { return mObd2Frame.get(); }
+
+    VehiclePropertyStore* getPropertyStore() { return mPropertyStore.get(); }
+
+  private:
+    std::unique_ptr<FakeObd2Frame> mObd2Frame;
+    std::shared_ptr<VehiclePropertyStore> mPropertyStore;
+};
+
+TEST_F(FakeObd2FrameTest, testIsDiagnosticPropertyTrue) {
+    for (auto prop : std::vector<int32_t>({
+                 OBD2_LIVE_FRAME,
+                 OBD2_FREEZE_FRAME,
+                 OBD2_FREEZE_FRAME_CLEAR,
+                 OBD2_FREEZE_FRAME_INFO,
+         })) {
+        EXPECT_TRUE(FakeObd2Frame::isDiagnosticProperty(VehiclePropConfig{
+                .prop = prop,
+        }));
+    }
+}
+
+TEST_F(FakeObd2FrameTest, testIsDiagnosticPropertyFalse) {
+    ASSERT_FALSE(FakeObd2Frame::isDiagnosticProperty(VehiclePropConfig{
+            .prop = toInt(VehicleProperty::INFO_VIN),
+    }));
+}
+
+TEST_F(FakeObd2FrameTest, testInitObd2LiveFrame) {
+    int64_t timestamp = elapsedRealtimeNano();
+
+    getFakeObd2Frame()->initObd2LiveFrame(getObd2LiveFrameConfig());
+
+    auto result = getPropertyStore()->readValue(OBD2_LIVE_FRAME);
+
+    ASSERT_TRUE(result.ok());
+    auto& value = result.value();
+
+    EXPECT_GE(value->timestamp, timestamp);
+    EXPECT_EQ(value->value.stringValue, "");
+    EXPECT_EQ(value->value.int32Values.size(), static_cast<size_t>(33));
+    EXPECT_EQ(value->value.floatValues.size(), static_cast<size_t>(72));
+}
+
+TEST_F(FakeObd2FrameTest, testInitFreezeFrame) {
+    getFakeObd2Frame()->initObd2FreezeFrame(getObd2FreezeFrameConfig());
+
+    auto result = getPropertyStore()->readValuesForProperty(OBD2_FREEZE_FRAME);
+
+    ASSERT_TRUE(result.ok());
+    ASSERT_EQ(result.value().size(), static_cast<size_t>(3));
+}
+
+TEST_F(FakeObd2FrameTest, testGetObd2DtcInfo) {
+    getFakeObd2Frame()->initObd2FreezeFrame(getObd2FreezeFrameConfig());
+
+    auto result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    EXPECT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(3));
+}
+
+TEST_F(FakeObd2FrameTest, testGetObd2FreezeFrame) {
+    getFakeObd2Frame()->initObd2FreezeFrame(getObd2FreezeFrameConfig());
+
+    auto result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    ASSERT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    ASSERT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(3));
+
+    std::set<std::string> dtcs;
+
+    for (int64_t timestamp : result.value()->value.int64Values) {
+        auto freezeFrameResult = getFakeObd2Frame()->getObd2FreezeFrame(VehiclePropValue{
+                .value.int64Values = {timestamp},
+        });
+
+        ASSERT_TRUE(freezeFrameResult.ok());
+
+        dtcs.insert(freezeFrameResult.value()->value.stringValue);
+    }
+
+    ASSERT_EQ(dtcs, std::set<std::string>({"P0070", "P0102", "P0123"}));
+}
+
+TEST_F(FakeObd2FrameTest, testClearObd2FreezeFrameAll) {
+    getFakeObd2Frame()->initObd2FreezeFrame(getObd2FreezeFrameConfig());
+
+    auto result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    ASSERT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    ASSERT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(3));
+
+    ASSERT_TRUE(getFakeObd2Frame()->clearObd2FreezeFrames(VehiclePropValue{}).ok());
+
+    result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    EXPECT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(0));
+}
+
+TEST_F(FakeObd2FrameTest, testClearObd2FreezeFrameByTimestamp) {
+    getFakeObd2Frame()->initObd2FreezeFrame(getObd2FreezeFrameConfig());
+
+    auto result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    ASSERT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    ASSERT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(3));
+
+    ASSERT_TRUE(getFakeObd2Frame()
+                        ->clearObd2FreezeFrames(VehiclePropValue{
+                                .value.int64Values = {result.value()->value.int64Values[0],
+                                                      result.value()->value.int64Values[1]}})
+                        .ok());
+
+    result = getFakeObd2Frame()->getObd2DtcInfo();
+
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value()->prop, OBD2_FREEZE_FRAME_INFO);
+    EXPECT_EQ(result.value()->value.int64Values.size(), static_cast<size_t>(1));
+}
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Obd2SensorStoreTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Obd2SensorStoreTest.cpp
new file mode 100644
index 0000000..23ea51d
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/obd2frame/test/Obd2SensorStoreTest.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "Obd2SensorStore.h"
+
+#include <VehicleUtils.h>
+
+#include <gtest/gtest.h>
+#include <utils/SystemClock.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace obd2frame {
+
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticFloatSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::DiagnosticIntegerSensorIndex;
+using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+
+TEST(Obd2SensorStoreTest, testObd2SensorStore) {
+    int64_t timestamp = elapsedRealtimeNano();
+    std::shared_ptr<VehiclePropValuePool> valuePool = std::make_shared<VehiclePropValuePool>();
+    Obd2SensorStore sensorStore(valuePool, 1, 1);
+
+    DiagnosticIntegerSensorIndex systemIntSensorIndex =
+            DiagnosticIntegerSensorIndex::IGNITION_MONITORS_SUPPORTED;
+    size_t vendorIntSensorIndex = Obd2SensorStore::getLastIndex<DiagnosticIntegerSensorIndex>() + 1;
+    DiagnosticFloatSensorIndex systemFloatSensorIndex =
+            DiagnosticFloatSensorIndex::SHORT_TERM_FUEL_TRIM_BANK1;
+    size_t vendorFloatSensorIndex = Obd2SensorStore::getLastIndex<DiagnosticFloatSensorIndex>() + 1;
+    // Four 1s in all the bits.
+    std::vector<uint8_t> bitMask = {0x4, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0,
+                                    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1};
+
+    ASSERT_EQ(sensorStore.setIntegerSensor(systemIntSensorIndex, 1), StatusCode::OK);
+    ASSERT_EQ(sensorStore.setIntegerSensor(vendorIntSensorIndex, 2), StatusCode::OK);
+    ASSERT_EQ(sensorStore.setFloatSensor(systemFloatSensorIndex, 3.0), StatusCode::OK);
+    ASSERT_EQ(sensorStore.setFloatSensor(vendorFloatSensorIndex, 4.0), StatusCode::OK);
+
+    std::string dtc = "dtc";
+    auto propValue = sensorStore.getSensorProperty(dtc);
+
+    ASSERT_GE(propValue->timestamp, timestamp);
+    ASSERT_EQ(propValue->value.int32Values[toInt(systemIntSensorIndex)], 1);
+    ASSERT_EQ(propValue->value.int32Values[vendorIntSensorIndex], 2);
+    ASSERT_EQ(propValue->value.floatValues[toInt(systemFloatSensorIndex)], 3.0);
+    ASSERT_EQ(propValue->value.floatValues[vendorFloatSensorIndex], 4.0);
+    ASSERT_EQ(propValue->value.byteValues, bitMask);
+    ASSERT_EQ(propValue->value.stringValue, dtc);
+}
+
+TEST(Obd2SensorStoreTest, testIndexOOB) {
+    std::shared_ptr<VehiclePropValuePool> valuePool = std::make_shared<VehiclePropValuePool>();
+    Obd2SensorStore sensorStore(valuePool, 1, 1);
+
+    EXPECT_EQ(sensorStore.setIntegerSensor(
+                      Obd2SensorStore::getLastIndex<DiagnosticIntegerSensorIndex>() + 2, 1),
+              StatusCode::INVALID_ARG);
+    EXPECT_EQ(sensorStore.setIntegerSensor(static_cast<size_t>(-1), 1), StatusCode::INVALID_ARG);
+    EXPECT_EQ(sensorStore.setFloatSensor(
+                      Obd2SensorStore::getLastIndex<DiagnosticFloatSensorIndex>() + 2, 1.0),
+              StatusCode::INVALID_ARG);
+    EXPECT_EQ(sensorStore.setFloatSensor(static_cast<size_t>(-1), 1.0), StatusCode::INVALID_ARG);
+}
+
+}  // namespace obd2frame
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/userhal/Android.bp
new file mode 100644
index 0000000..2e95531
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/Android.bp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "FakeUserHal",
+    vendor: true,
+    srcs: ["src/*.cpp"],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    defaults: ["VehicleHalDefaults"],
+    static_libs: [
+        "VehicleHalUtils",
+    ],
+    export_static_lib_headers: ["VehicleHalUtils"],
+}
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/include/FakeUserHal.h b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/FakeUserHal.h
new file mode 100644
index 0000000..1424c81
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/FakeUserHal.h
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_FakeUserHal_H_
+#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_FakeUserHal_H_
+
+#include <android-base/format.h>
+#include <android-base/result.h>
+#include <android-base/thread_annotations.h>
+
+#include <VehicleHalTypes.h>
+#include <VehicleObjectPool.h>
+
+#include <memory>
+#include <mutex>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+
+constexpr char kUserHalDumpOption[] = "--user-hal";
+
+// Class used to emulate a real User HAL behavior through lshal debug requests.
+class FakeUserHal final {
+  public:
+    explicit FakeUserHal(std::shared_ptr<VehiclePropValuePool> valuePool) : mValuePool(valuePool) {}
+
+    ~FakeUserHal() = default;
+
+    // Checks if the emulator can handle the property.
+    static bool isSupported(int32_t prop);
+
+    // Lets the emulator set the property.
+    //
+    // @return updated property and StatusCode
+    android::base::Result<VehiclePropValuePool::RecyclableType> onSetProperty(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+
+    // Gets the property value from the emulator.
+    //
+    // @return property value and StatusCode
+    android::base::Result<VehiclePropValuePool::RecyclableType> onGetProperty(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
+
+    // Shows the User HAL emulation help.
+    std::string showDumpHelp() const;
+
+    // Dump its contents.
+    std::string dump(std::string indent) const;
+
+  private:
+    const std::shared_ptr<VehiclePropValuePool> mValuePool;
+    mutable std::mutex mLock;
+    VehiclePropValuePool::RecyclableType mInitialUserResponseFromCmd GUARDED_BY(mLock);
+    VehiclePropValuePool::RecyclableType mSwitchUserResponseFromCmd GUARDED_BY(mLock);
+    VehiclePropValuePool::RecyclableType mCreateUserResponseFromCmd GUARDED_BY(mLock);
+    VehiclePropValuePool::RecyclableType mSetUserIdentificationAssociationResponseFromCmd
+            GUARDED_BY(mLock);
+
+    // INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change
+    // indicating what the initial user should be.
+    //
+    // During normal circumstances, the emulator will reply right away, passing a response if
+    // InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which
+    // user to boot).
+    //
+    // But during development / testing, the behavior can be changed using lshal dump, which must
+    // use the areaId to indicate what should happen next.
+    //
+    // So, the behavior of set(INITIAL_USER_INFO) is:
+    //
+    // - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called
+    // by lshal).
+    // - else if mInitialUserResponseFromCmd is not set, return a response with the same request id
+    // and InitialUserInfoResponseAction::DEFAULT
+    // - else the behavior is defined by the areaId on mInitialUserResponseFromCmd:
+    // - if it's 1, reply with mInitialUserResponseFromCmd and the right request id
+    // - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can
+    // test this error scenario)
+    // - if it's 3, then don't send a property change (so Android can emulate a timeout)
+    android::base::Result<VehiclePropValuePool::RecyclableType> onSetInitialUserInfoResponse(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+
+    // Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage.
+    android::base::Result<VehiclePropValuePool::RecyclableType> onSetSwitchUserResponse(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+
+    // Used to emulate CREATE_USER - see onSetInitialUserInfoResponse() for usage.
+    android::base::Result<VehiclePropValuePool::RecyclableType> onSetCreateUserResponse(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+
+    // Used to emulate set USER_IDENTIFICATION_ASSOCIATION - see onSetInitialUserInfoResponse() for
+    // usage.
+    android::base::Result<VehiclePropValuePool::RecyclableType> onSetUserIdentificationAssociation(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+
+    // Used to emulate get USER_IDENTIFICATION_ASSOCIATION - see onSetInitialUserInfoResponse() for
+    // usage.
+    android::base::Result<VehiclePropValuePool::RecyclableType> onGetUserIdentificationAssociation(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
+
+    // Creates a default USER_IDENTIFICATION_ASSOCIATION when it was not set by lshal.
+    static android::base::Result<VehiclePropValuePool::RecyclableType>
+    defaultUserIdentificationAssociation(
+            const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& request);
+
+    android::base::Result<VehiclePropValuePool::RecyclableType> sendUserHalResponse(
+            VehiclePropValuePool::RecyclableType response, int32_t requestId);
+};
+
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_FakeUserHal_H_
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalHelper.h b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalHelper.h
new file mode 100644
index 0000000..5be13be
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalHelper.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalHelper_H_
+#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalHelper_H_
+
+#include <UserHalTypes.h>
+#include <VehicleHalTypes.h>
+#include <VehicleObjectPool.h>
+#include <android-base/result.h>
+
+#include <functional>
+#include <memory>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace user_hal_helper {
+
+// Verify whether the |value| can be casted to the type |T| and return the casted value on success.
+// Otherwise, return the error.
+template <typename T>
+::android::base::Result<T> verifyAndCast(int32_t value);
+
+// Below functions parse VehiclePropValues to the respective User HAL request structs. On success,
+// these functions return the User HAL struct. Otherwise, they return the error.
+::android::base::Result<::aidl::android::hardware::automotive::vehicle::InitialUserInfoRequest>
+toInitialUserInfoRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+::android::base::Result<::aidl::android::hardware::automotive::vehicle::SwitchUserRequest>
+toSwitchUserRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+::android::base::Result<::aidl::android::hardware::automotive::vehicle::CreateUserRequest>
+toCreateUserRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+::android::base::Result<::aidl::android::hardware::automotive::vehicle::RemoveUserRequest>
+toRemoveUserRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+::android::base::Result<
+        ::aidl::android::hardware::automotive::vehicle::UserIdentificationGetRequest>
+toUserIdentificationGetRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+::android::base::Result<
+        ::aidl::android::hardware::automotive::vehicle::UserIdentificationSetRequest>
+toUserIdentificationSetRequest(
+        const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+
+// Below functions convert the User HAL structs to VehiclePropValues. On success, these functions
+// return the pointer to VehiclePropValue. Otherwise, they return the error.
+::android::base::Result<VehiclePropValuePool::RecyclableType> toVehiclePropValue(
+        VehiclePropValuePool& pool,
+        const ::aidl::android::hardware::automotive::vehicle::SwitchUserRequest& request);
+VehiclePropValuePool::RecyclableType toVehiclePropValue(
+        VehiclePropValuePool& pool,
+        const ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponse& response);
+VehiclePropValuePool::RecyclableType toVehiclePropValue(
+        VehiclePropValuePool& pool,
+        const ::aidl::android::hardware::automotive::vehicle::SwitchUserResponse& response);
+VehiclePropValuePool::RecyclableType toVehiclePropValue(
+        VehiclePropValuePool& pool,
+        const ::aidl::android::hardware::automotive::vehicle::CreateUserResponse& response);
+VehiclePropValuePool::RecyclableType toVehiclePropValue(
+        VehiclePropValuePool& pool,
+        const ::aidl::android::hardware::automotive::vehicle::UserIdentificationResponse& response);
+
+}  // namespace user_hal_helper
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalHelper_H_
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalTypes.h b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalTypes.h
new file mode 100644
index 0000000..b5e17b3
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/include/UserHalTypes.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalTypes_H_
+#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalTypes_H_
+
+#include <aidl/android/hardware/automotive/vehicle/CreateUserRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/CreateUserResponse.h>
+#include <aidl/android/hardware/automotive/vehicle/CreateUserStatus.h>
+#include <aidl/android/hardware/automotive/vehicle/InitialUserInfoRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/InitialUserInfoRequestType.h>
+#include <aidl/android/hardware/automotive/vehicle/InitialUserInfoResponse.h>
+#include <aidl/android/hardware/automotive/vehicle/RemoveUserRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/SwitchUserMessageType.h>
+#include <aidl/android/hardware/automotive/vehicle/SwitchUserRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/SwitchUserResponse.h>
+#include <aidl/android/hardware/automotive/vehicle/SwitchUserStatus.h>
+#include <aidl/android/hardware/automotive/vehicle/UserIdentificationGetRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/UserIdentificationResponse.h>
+#include <aidl/android/hardware/automotive/vehicle/UserIdentificationSetRequest.h>
+#include <aidl/android/hardware/automotive/vehicle/UserInfo.h>
+#include <aidl/android/hardware/automotive/vehicle/UsersInfo.h>
+
+#endif  // android_hardware_automotive_vehicle_aidl_impl_fake_impl_userhal_include_UserHalTypes_H_
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/src/FakeUserHal.cpp b/automotive/vehicle/aidl/impl/fake_impl/userhal/src/FakeUserHal.cpp
new file mode 100644
index 0000000..9b60053
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/src/FakeUserHal.cpp
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2020 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 "FakeUserHal"
+
+#include "FakeUserHal.h"
+
+#include "UserHalHelper.h"
+
+#include <VehicleUtils.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+
+namespace {
+
+using ::aidl::android::hardware::automotive::vehicle::CreateUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::CreateUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponse;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponseAction;
+using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserMessageType;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+using ::android::base::Error;
+using ::android::base::Result;
+
+constexpr int32_t INITIAL_USER_INFO = toInt(VehicleProperty::INITIAL_USER_INFO);
+constexpr int32_t SWITCH_USER = toInt(VehicleProperty::SWITCH_USER);
+constexpr int32_t CREATE_USER = toInt(VehicleProperty::CREATE_USER);
+constexpr int32_t REMOVE_USER = toInt(VehicleProperty::REMOVE_USER);
+constexpr int32_t USER_IDENTIFICATION_ASSOCIATION =
+        toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION);
+
+Result<int32_t> getRequestId(const VehiclePropValue& value) {
+    if (value.value.int32Values.size() < 1) {
+        return Error(toInt(StatusCode::INVALID_ARG))
+               << "no int32Values on property: " << value.toString();
+    }
+    return value.value.int32Values[0];
+}
+
+Result<SwitchUserMessageType> getSwitchUserMessageType(const VehiclePropValue& value) {
+    if (value.value.int32Values.size() < 2) {
+        return Error(toInt(StatusCode::INVALID_ARG))
+               << "missing switch user message type on property: " << value.toString();
+    }
+    return user_hal_helper::verifyAndCast<SwitchUserMessageType>(value.value.int32Values[1]);
+}
+
+}  // namespace
+
+bool FakeUserHal::isSupported(int32_t prop) {
+    switch (prop) {
+        case INITIAL_USER_INFO:
+        case SWITCH_USER:
+        case CREATE_USER:
+        case REMOVE_USER:
+        case USER_IDENTIFICATION_ASSOCIATION:
+            return true;
+        default:
+            return false;
+    }
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onSetProperty(
+        const VehiclePropValue& value) {
+    ALOGV("onSetProperty(): %s", value.toString().c_str());
+
+    switch (value.prop) {
+        case INITIAL_USER_INFO:
+            return onSetInitialUserInfoResponse(value);
+        case SWITCH_USER:
+            return onSetSwitchUserResponse(value);
+        case CREATE_USER:
+            return onSetCreateUserResponse(value);
+        case REMOVE_USER:
+            ALOGI("REMOVE_USER is FYI only, nothing to do...");
+            return nullptr;
+        case USER_IDENTIFICATION_ASSOCIATION:
+            return onSetUserIdentificationAssociation(value);
+        default:
+            return Error(toInt(StatusCode::INVALID_ARG))
+                   << "Unsupported property: " << value.toString();
+    }
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onGetProperty(
+        const VehiclePropValue& value) const {
+    ALOGV("onGetProperty(%s)", value.toString().c_str());
+    switch (value.prop) {
+        case INITIAL_USER_INFO:
+        case SWITCH_USER:
+        case CREATE_USER:
+        case REMOVE_USER:
+            ALOGE("onGetProperty(): %d is only supported on SET", value.prop);
+            return Error(toInt(StatusCode::INVALID_ARG)) << "only supported on SET";
+        case USER_IDENTIFICATION_ASSOCIATION:
+            return onGetUserIdentificationAssociation(value);
+        default:
+            ALOGE("onGetProperty(): %d is not supported", value.prop);
+            return Error(toInt(StatusCode::INVALID_ARG)) << "not supported by User HAL";
+    }
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onGetUserIdentificationAssociation(
+        const VehiclePropValue& value) const {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    if (mSetUserIdentificationAssociationResponseFromCmd == nullptr) {
+        return defaultUserIdentificationAssociation(value);
+    }
+    ALOGI("get(USER_IDENTIFICATION_ASSOCIATION): returning %s",
+          mSetUserIdentificationAssociationResponseFromCmd->toString().c_str());
+    auto newValue = mValuePool->obtain(*mSetUserIdentificationAssociationResponseFromCmd);
+    auto requestId = getRequestId(value);
+    if (requestId.ok()) {
+        // Must use the same requestId
+        newValue->value.int32Values[0] = *requestId;
+    } else {
+        ALOGE("get(USER_IDENTIFICATION_ASSOCIATION): no requestId on %s", value.toString().c_str());
+        return requestId.error();
+    }
+    return newValue;
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onSetInitialUserInfoResponse(
+        const VehiclePropValue& value) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    auto requestId = getRequestId(value);
+    if (!requestId.ok()) {
+        ALOGE("Failed to get requestId on set(INITIAL_USER_INFO): %s",
+              requestId.error().message().c_str());
+        return requestId.error();
+    }
+
+    if (value.areaId != 0) {
+        ALOGD("set(INITIAL_USER_INFO) called from lshal; storing it: %s", value.toString().c_str());
+        mInitialUserResponseFromCmd = mValuePool->obtain(value);
+        return nullptr;
+    }
+
+    ALOGD("set(INITIAL_USER_INFO) called from Android: %s", value.toString().c_str());
+    if (mInitialUserResponseFromCmd != nullptr) {
+        ALOGI("replying INITIAL_USER_INFO with lshal value: %s",
+              mInitialUserResponseFromCmd->toString().c_str());
+        return sendUserHalResponse(std::move(mInitialUserResponseFromCmd), *requestId);
+    }
+
+    // Returns default response
+    auto updatedValue = user_hal_helper::toVehiclePropValue(
+            *mValuePool, InitialUserInfoResponse{
+                                 .requestId = *requestId,
+                                 .action = InitialUserInfoResponseAction::DEFAULT,
+                         });
+    ALOGI("no lshal response; replying with InitialUserInfoResponseAction::DEFAULT: %s",
+          updatedValue->toString().c_str());
+    return updatedValue;
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onSetSwitchUserResponse(
+        const VehiclePropValue& value) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    auto requestId = getRequestId(value);
+    if (!requestId.ok()) {
+        ALOGE("Failed to get requestId on set(SWITCH_USER): %s",
+              requestId.error().message().c_str());
+        return requestId.error();
+    }
+
+    auto messageType = getSwitchUserMessageType(value);
+    if (!messageType.ok()) {
+        ALOGE("Failed to get messageType on set(SWITCH_USER): %s",
+              messageType.error().message().c_str());
+        return messageType.error();
+    }
+
+    if (value.areaId != 0) {
+        if (*messageType == SwitchUserMessageType::VEHICLE_REQUEST) {
+            // User HAL can also request a user switch, so we need to check it first
+            ALOGD("set(SWITCH_USER) called from lshal to emulate a vehicle request: %s",
+                  value.toString().c_str());
+            return mValuePool->obtain(value);
+        }
+        // Otherwise, we store it
+        ALOGD("set(SWITCH_USER) called from lshal; storing it: %s", value.toString().c_str());
+        mSwitchUserResponseFromCmd = mValuePool->obtain(value);
+        return nullptr;
+    }
+    ALOGD("set(SWITCH_USER) called from Android: %s", value.toString().c_str());
+
+    if (mSwitchUserResponseFromCmd != nullptr) {
+        ALOGI("replying SWITCH_USER with lshal value:  %s",
+              mSwitchUserResponseFromCmd->toString().c_str());
+        return sendUserHalResponse(std::move(mSwitchUserResponseFromCmd), *requestId);
+    }
+
+    if (*messageType == SwitchUserMessageType::LEGACY_ANDROID_SWITCH ||
+        *messageType == SwitchUserMessageType::ANDROID_POST_SWITCH) {
+        ALOGI("request is %s; ignoring it", toString(*messageType).c_str());
+        return nullptr;
+    }
+
+    // Returns default response
+    auto updatedValue = user_hal_helper::toVehiclePropValue(
+            *mValuePool, SwitchUserResponse{
+                                 .requestId = *requestId,
+                                 .messageType = SwitchUserMessageType::VEHICLE_RESPONSE,
+                                 .status = SwitchUserStatus::SUCCESS,
+                         });
+    ALOGI("no lshal response; replying with VEHICLE_RESPONSE / SUCCESS: %s",
+          updatedValue->toString().c_str());
+    return updatedValue;
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onSetCreateUserResponse(
+        const VehiclePropValue& value) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    auto requestId = getRequestId(value);
+    if (!requestId.ok()) {
+        ALOGE("Failed to get requestId on set(CREATE_USER): %s",
+              requestId.error().message().c_str());
+        return requestId.error();
+    }
+
+    if (value.areaId != 0) {
+        ALOGD("set(CREATE_USER) called from lshal; storing it: %s", value.toString().c_str());
+        mCreateUserResponseFromCmd = mValuePool->obtain(value);
+        return nullptr;
+    }
+    ALOGD("set(CREATE_USER) called from Android: %s", value.toString().c_str());
+
+    if (mCreateUserResponseFromCmd != nullptr) {
+        ALOGI("replying CREATE_USER with lshal value:  %s",
+              mCreateUserResponseFromCmd->toString().c_str());
+        return sendUserHalResponse(std::move(mCreateUserResponseFromCmd), *requestId);
+    }
+
+    // Returns default response
+    auto updatedValue = user_hal_helper::toVehiclePropValue(
+            *mValuePool, CreateUserResponse{
+                                 .requestId = *requestId,
+                                 .status = CreateUserStatus::SUCCESS,
+                         });
+    ALOGI("no lshal response; replying with SUCCESS: %s", updatedValue->toString().c_str());
+    return updatedValue;
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::onSetUserIdentificationAssociation(
+        const VehiclePropValue& value) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    auto requestId = getRequestId(value);
+    if (!requestId.ok()) {
+        ALOGE("Failed to get requestId on set(USER_IDENTIFICATION_ASSOCIATION): %s",
+              requestId.error().message().c_str());
+        return requestId.error();
+    }
+
+    if (value.areaId != 0) {
+        ALOGD("set(USER_IDENTIFICATION_ASSOCIATION) called from lshal; storing it: %s",
+              value.toString().c_str());
+        mSetUserIdentificationAssociationResponseFromCmd = mValuePool->obtain(value);
+        return nullptr;
+    }
+    ALOGD("set(USER_IDENTIFICATION_ASSOCIATION) called from Android: %s", value.toString().c_str());
+
+    if (mSetUserIdentificationAssociationResponseFromCmd != nullptr) {
+        ALOGI("replying USER_IDENTIFICATION_ASSOCIATION with lshal value:  %s",
+              mSetUserIdentificationAssociationResponseFromCmd->toString().c_str());
+        // Not moving response so it can be used on GET requests
+        auto copy = mValuePool->obtain(*mSetUserIdentificationAssociationResponseFromCmd);
+        return sendUserHalResponse(std::move(copy), *requestId);
+    }
+    // Returns default response
+    return defaultUserIdentificationAssociation(value);
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::defaultUserIdentificationAssociation(
+        const VehiclePropValue& request) {
+    // TODO(b/159498909): return a response with NOT_ASSOCIATED_ANY_USER for all requested types
+    ALOGE("no lshal response for %s; replying with NOT_AVAILABLE", request.toString().c_str());
+    return Error(toInt(StatusCode::NOT_AVAILABLE)) << "not set by lshal";
+}
+
+Result<VehiclePropValuePool::RecyclableType> FakeUserHal::sendUserHalResponse(
+        VehiclePropValuePool::RecyclableType response, int32_t requestId) {
+    switch (response->areaId) {
+        case 1:
+            ALOGD("returning response with right request id");
+            response->value.int32Values[0] = requestId;
+            break;
+        case 2:
+            ALOGD("returning response with wrong request id");
+            response->value.int32Values[0] = -requestId;
+            break;
+        case 3:
+            ALOGD("not generating a property change event because of lshal prop: %s",
+                  response->toString().c_str());
+            return Error(toInt(StatusCode::NOT_AVAILABLE))
+                   << "not generating a property change event because of lshal prop: "
+                   << response->toString();
+        default:
+            ALOGE("invalid action on lshal response: %s", response->toString().c_str());
+            return Error(toInt(StatusCode::INTERNAL_ERROR))
+                   << "invalid action on lshal response: " << response->toString();
+    }
+
+    ALOGD("updating property to: %s", response->toString().c_str());
+    return response;
+}
+
+std::string FakeUserHal::showDumpHelp() const {
+    return fmt::format("{}: dumps state used for user management\n", kUserHalDumpOption);
+}
+
+std::string FakeUserHal::dump(std::string indent) const {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    std::string info;
+    if (mInitialUserResponseFromCmd != nullptr) {
+        info += fmt::format("{}InitialUserInfo response: {}\n", indent,
+                            mInitialUserResponseFromCmd->toString());
+    } else {
+        info += fmt::format("{}No InitialUserInfo response\n", indent);
+    }
+    if (mSwitchUserResponseFromCmd != nullptr) {
+        info += fmt::format("{}SwitchUser response: {}\n", indent,
+                            mSwitchUserResponseFromCmd->toString());
+    } else {
+        info += fmt::format("{}No SwitchUser response\n", indent);
+    }
+    if (mCreateUserResponseFromCmd != nullptr) {
+        info += fmt::format("{}CreateUser response: {}\n", indent,
+                            mCreateUserResponseFromCmd->toString());
+    } else {
+        info += fmt::format("{}No CreateUser response\n", indent);
+    }
+    if (mSetUserIdentificationAssociationResponseFromCmd != nullptr) {
+        info += fmt::format("{}SetUserIdentificationAssociation response: {}\n", indent,
+                            mSetUserIdentificationAssociationResponseFromCmd->toString());
+    } else {
+        info += fmt::format("{}No SetUserIdentificationAssociation response\n", indent);
+    }
+    return info;
+}
+
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/src/UserHalHelper.cpp b/automotive/vehicle/aidl/impl/fake_impl/userhal/src/UserHalHelper.cpp
new file mode 100644
index 0000000..4fd93ae
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/src/UserHalHelper.cpp
@@ -0,0 +1,403 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UserHalHelper"
+
+#include "UserHalHelper.h"
+
+#include <VehicleUtils.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+
+#include <vector>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace user_hal_helper {
+
+namespace {
+
+using ::aidl::android::hardware::automotive::vehicle::CreateUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::CreateUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::CreateUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoRequest;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoRequestType;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponse;
+using ::aidl::android::hardware::automotive::vehicle::RemoveUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserMessageType;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationAssociationSetValue;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationAssociationType;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationGetRequest;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationResponse;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationSetAssociation;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationSetRequest;
+using ::aidl::android::hardware::automotive::vehicle::UserInfo;
+using ::aidl::android::hardware::automotive::vehicle::UsersInfo;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+using ::android::base::Error;
+using ::android::base::Result;
+
+constexpr const char kSeparator[] = "||";
+constexpr size_t kNumFieldsPerUserInfo = 2;
+constexpr size_t kNumFieldsPerSetAssociation = 2;
+
+Result<void> verifyPropValue(const VehiclePropValue& propValue, VehicleProperty vehicleProperty,
+                             size_t minInt32Values) {
+    auto prop = verifyAndCast<VehicleProperty>(propValue.prop);
+    if (!prop.ok()) {
+        return Error() << "Invalid vehicle property: " << prop.error();
+    }
+    if (*prop != vehicleProperty) {
+        return Error() << "Mismatching " << toString(vehicleProperty) << " request, received "
+                       << toString(*prop) << " property";
+    }
+    if (propValue.value.int32Values.size() < minInt32Values) {
+        return Error() << "Int32Values must have at least " << minInt32Values
+                       << " values, received " << propValue.value.int32Values.size();
+    }
+    return {};
+}
+
+Result<void> parseUserInfo(const std::vector<int32_t>& int32Values, size_t startPos,
+                           UserInfo* userInfo) {
+    if (int32Values.size() < startPos + kNumFieldsPerUserInfo) {
+        return Error() << "Int32Values must have at least " << startPos + 2 << " values, received "
+                       << int32Values.size();
+    }
+    userInfo->userId = int32Values[startPos];
+    int32_t intUserFlags = int32Values[startPos + 1];
+    const int32_t combinedFlags = UserInfo::USER_FLAG_SYSTEM | UserInfo::USER_FLAG_GUEST |
+                                  UserInfo::USER_FLAG_EPHEMERAL | UserInfo::USER_FLAG_ADMIN |
+                                  UserInfo::USER_FLAG_DISABLED | UserInfo::USER_FLAG_PROFILE;
+
+    if ((intUserFlags & ~combinedFlags) != 0) {
+        return Error() << "Invalid user flags: " << intUserFlags << ", must be '|' of UserFlags";
+    }
+    userInfo->flags = intUserFlags;
+    return {};
+}
+
+Result<void> parseUsersInfo(const std::vector<int32_t>& int32Values, size_t startPos,
+                            UsersInfo* usersInfo) {
+    if (int32Values.size() < startPos + 3) {
+        return Error() << "Int32Values must have at least " << startPos + 3 << " values, received "
+                       << int32Values.size();
+    }
+    auto ret = parseUserInfo(int32Values, startPos, &usersInfo->currentUser);
+    if (!ret.ok()) {
+        return ret;
+    }
+    usersInfo->numberUsers = int32Values[startPos + 2];
+    usersInfo->existingUsers.resize(usersInfo->numberUsers);
+    for (size_t i = 0; i < static_cast<size_t>(usersInfo->numberUsers); ++i) {
+        ret = parseUserInfo(int32Values, startPos + 3 + (kNumFieldsPerUserInfo * i),
+                            &usersInfo->existingUsers[i]);
+        if (!ret.ok()) {
+            return Error() << "Failed to parse existing user '" << i << "' info: " << ret.error();
+        }
+    }
+    return {};
+}
+
+Result<void> parseUserAssociationTypes(
+        const std::vector<int32_t>& int32Values, size_t startPos, size_t numberAssociationTypes,
+        std::vector<UserIdentificationAssociationType>* associationTypes) {
+    size_t minInt32Values = startPos + numberAssociationTypes;
+    if (int32Values.size() < minInt32Values) {
+        return Error() << "Int32Values must have at least " << minInt32Values
+                       << " values, received " << int32Values.size();
+    }
+    associationTypes->resize(numberAssociationTypes);
+    for (size_t i = 0; i < static_cast<size_t>(numberAssociationTypes); ++i) {
+        size_t pos = startPos + i;
+        auto type = verifyAndCast<UserIdentificationAssociationType>(int32Values[pos]);
+        if (!type.ok()) {
+            return Error() << "Invalid association type in query '" << i << "': " << type.error();
+        }
+        (*associationTypes)[i] = *type;
+    }
+    return {};
+}
+
+Result<void> parseUserAssociations(const std::vector<int32_t>& int32Values, size_t startPos,
+                                   size_t numberAssociations,
+                                   std::vector<UserIdentificationSetAssociation>* associations) {
+    size_t minInt32Values = startPos + (numberAssociations * kNumFieldsPerSetAssociation);
+    if (int32Values.size() < minInt32Values) {
+        return Error() << "Int32Values must have at least " << minInt32Values
+                       << " values, received " << int32Values.size();
+    }
+    associations->resize(numberAssociations);
+    for (size_t i = 0; i < static_cast<size_t>(numberAssociations); ++i) {
+        size_t pos = startPos + (kNumFieldsPerSetAssociation * i);
+        auto type = verifyAndCast<UserIdentificationAssociationType>(int32Values[pos]);
+        if (!type.ok()) {
+            return Error() << "Invalid association type in request '" << i << "': " << type.error();
+        }
+        (*associations)[i].type = *type;
+        auto value = verifyAndCast<UserIdentificationAssociationSetValue>(int32Values[pos + 1]);
+        if (!value.ok()) {
+            return Error() << "Invalid association set value in request '" << i
+                           << "': " << value.error();
+        }
+        (*associations)[i].value = *value;
+    }
+    return {};
+}
+
+}  // namespace
+
+template <typename T>
+Result<T> verifyAndCast(int32_t value) {
+    T castValue = static_cast<T>(value);
+    for (const auto& v : ::ndk::enum_range<T>()) {
+        if (castValue == v) {
+            return castValue;
+        }
+    }
+
+    return Error() << "Value " << value << " not in enum values";
+}
+
+Result<InitialUserInfoRequest> toInitialUserInfoRequest(const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::INITIAL_USER_INFO, /*minInt32Values=*/2);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    InitialUserInfoRequest request;
+    request.requestId = propValue.value.int32Values[0];
+    auto requestType = verifyAndCast<InitialUserInfoRequestType>(propValue.value.int32Values[1]);
+    if (!requestType.ok()) {
+        return Error() << "Invalid InitialUserInfoRequestType: " << requestType.error();
+    }
+    request.requestType = *requestType;
+    ret = parseUsersInfo(propValue.value.int32Values, 2, &request.usersInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse users info: " << ret.error();
+    }
+    return request;
+}
+
+Result<SwitchUserRequest> toSwitchUserRequest(const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::SWITCH_USER, /*minInt32Values=*/2);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    SwitchUserRequest request;
+    auto messageType = verifyAndCast<SwitchUserMessageType>(propValue.value.int32Values[1]);
+    if (!messageType.ok()) {
+        return Error() << "Invalid SwitchUserMessageType: " << messageType.error();
+    }
+    if (*messageType != SwitchUserMessageType::LEGACY_ANDROID_SWITCH &&
+        *messageType != SwitchUserMessageType::ANDROID_SWITCH &&
+        *messageType != SwitchUserMessageType::ANDROID_POST_SWITCH) {
+        return Error() << "Invalid " << toString(*messageType)
+                       << " message type from Android System";
+    }
+    request.requestId = propValue.value.int32Values[0];
+    request.messageType = *messageType;
+    ret = parseUserInfo(propValue.value.int32Values, /*startPos=*/2, &request.targetUser);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse target user info: " << ret.error();
+    }
+    ret = parseUsersInfo(propValue.value.int32Values, /*startPos=*/4, &request.usersInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse users info: " << ret.error();
+    }
+    return request;
+}
+
+Result<CreateUserRequest> toCreateUserRequest(const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::CREATE_USER, /*minInt32Values=*/1);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    CreateUserRequest request;
+    request.requestId = propValue.value.int32Values[0];
+    ret = parseUserInfo(propValue.value.int32Values, /*startPos=*/1, &request.newUserInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse new user info: " << ret.error();
+    }
+    request.newUserName = propValue.value.stringValue;
+    ret = parseUsersInfo(propValue.value.int32Values, /*startPos=*/3, &request.usersInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse users info: " << ret.error();
+    }
+    return request;
+}
+
+Result<RemoveUserRequest> toRemoveUserRequest(const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::REMOVE_USER, /*minInt32Values=*/1);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    RemoveUserRequest request;
+    request.requestId = propValue.value.int32Values[0];
+    ret = parseUserInfo(propValue.value.int32Values, /*startPos=*/1, &request.removedUserInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse removed user info: " << ret.error();
+    }
+    ret = parseUsersInfo(propValue.value.int32Values, /*startPos=*/3, &request.usersInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse users info: " << ret.error();
+    }
+    return request;
+}
+
+Result<UserIdentificationGetRequest> toUserIdentificationGetRequest(
+        const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::USER_IDENTIFICATION_ASSOCIATION,
+                               /*minInt32Values=*/4);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    UserIdentificationGetRequest request;
+    request.requestId = propValue.value.int32Values[0];
+    ret = parseUserInfo(propValue.value.int32Values, /*startPos=*/1, &request.userInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse user info: " << ret.error();
+    }
+    request.numberAssociationTypes = propValue.value.int32Values[3];
+    ret = parseUserAssociationTypes(propValue.value.int32Values, 4, request.numberAssociationTypes,
+                                    &request.associationTypes);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse UserIdentificationAssociationType: " << ret.error();
+    }
+    return request;
+}
+
+Result<UserIdentificationSetRequest> toUserIdentificationSetRequest(
+        const VehiclePropValue& propValue) {
+    auto ret = verifyPropValue(propValue, VehicleProperty::USER_IDENTIFICATION_ASSOCIATION,
+                               /*minInt32Values=*/4);
+    if (!ret.ok()) {
+        return ret.error();
+    }
+    UserIdentificationSetRequest request;
+    request.requestId = propValue.value.int32Values[0];
+    ret = parseUserInfo(propValue.value.int32Values, /*startPos=*/1, &request.userInfo);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse user info: " << ret.error();
+    }
+    request.numberAssociations = propValue.value.int32Values[3];
+    ret = parseUserAssociations(propValue.value.int32Values, 4, request.numberAssociations,
+                                &request.associations);
+    if (!ret.ok()) {
+        return Error() << "Failed to parse UserIdentificationSetAssociation: " << ret.error();
+    }
+    return request;
+}
+
+Result<VehiclePropValuePool::RecyclableType> toVehiclePropValue(VehiclePropValuePool& pool,
+                                                                const SwitchUserRequest& request) {
+    if (request.messageType != SwitchUserMessageType::VEHICLE_REQUEST) {
+        return Errorf("Invalid %s message type %s from HAL",
+                      toString(VehicleProperty::SWITCH_USER).c_str(),
+                      toString(request.messageType).c_str());
+    }
+    int32_t switchUserProp = toInt(VehicleProperty::SWITCH_USER);
+    auto propValue = pool.obtain(getPropType(switchUserProp));
+    propValue->prop = switchUserProp;
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values.resize(3);
+    propValue->value.int32Values[0] = static_cast<int32_t>(request.requestId);
+    propValue->value.int32Values[1] = static_cast<int32_t>(request.messageType);
+    propValue->value.int32Values[2] = static_cast<int32_t>(request.targetUser.userId);
+    return propValue;
+}
+
+VehiclePropValuePool::RecyclableType toVehiclePropValue(VehiclePropValuePool& pool,
+                                                        const InitialUserInfoResponse& response) {
+    int32_t initialUserInfoProp = toInt(VehicleProperty::INITIAL_USER_INFO);
+    auto propValue = pool.obtain(getPropType(initialUserInfoProp));
+    propValue->prop = initialUserInfoProp;
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values.resize(4);
+    propValue->value.int32Values[0] = static_cast<int32_t>(response.requestId);
+    propValue->value.int32Values[1] = static_cast<int32_t>(response.action);
+    propValue->value.int32Values[2] = static_cast<int32_t>(response.userToSwitchOrCreate.userId);
+    propValue->value.int32Values[3] = response.userToSwitchOrCreate.flags;
+    propValue->value.stringValue = std::string(response.userLocales) + std::string(kSeparator) +
+                                   std::string(response.userNameToCreate);
+    return propValue;
+}
+
+VehiclePropValuePool::RecyclableType toVehiclePropValue(VehiclePropValuePool& pool,
+                                                        const SwitchUserResponse& response) {
+    int32_t switchUserProp = toInt(VehicleProperty::SWITCH_USER);
+    auto propValue = pool.obtain(getPropType(switchUserProp));
+    propValue->prop = switchUserProp;
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values.resize(3);
+    propValue->value.int32Values[0] = static_cast<int32_t>(response.requestId);
+    propValue->value.int32Values[1] = static_cast<int32_t>(response.messageType);
+    propValue->value.int32Values[2] = static_cast<int32_t>(response.status);
+    if (response.status == SwitchUserStatus::FAILURE) {
+        propValue->value.stringValue = response.errorMessage;
+    }
+    return propValue;
+}
+
+VehiclePropValuePool::RecyclableType toVehiclePropValue(VehiclePropValuePool& pool,
+                                                        const CreateUserResponse& response) {
+    int32_t createUserProp = toInt(VehicleProperty::CREATE_USER);
+    auto propValue = pool.obtain(getPropType(createUserProp));
+    propValue->prop = createUserProp;
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values.resize(2);
+    propValue->value.int32Values[0] = static_cast<int32_t>(response.requestId);
+    propValue->value.int32Values[1] = static_cast<int32_t>(response.status);
+    if (response.status == CreateUserStatus::FAILURE) {
+        propValue->value.stringValue = response.errorMessage;
+    }
+    return propValue;
+}
+
+VehiclePropValuePool::RecyclableType toVehiclePropValue(
+        VehiclePropValuePool& pool, const UserIdentificationResponse& response) {
+    int32_t userIdAssocProp = toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION);
+    auto propValue = pool.obtain(getPropType(userIdAssocProp));
+    propValue->prop = userIdAssocProp;
+    propValue->timestamp = elapsedRealtimeNano();
+    propValue->value.int32Values.resize(2 + (response.numberAssociation * 2));
+    propValue->value.int32Values[0] = static_cast<int32_t>(response.requestId);
+    propValue->value.int32Values[1] = static_cast<int32_t>(response.numberAssociation);
+    for (size_t i = 0; i < static_cast<size_t>(response.numberAssociation); ++i) {
+        size_t int32ValuesPos = 2 + (2 * i);
+        propValue->value.int32Values[int32ValuesPos] =
+                static_cast<int32_t>(response.associations[i].type);
+        propValue->value.int32Values[int32ValuesPos + 1] =
+                static_cast<int32_t>(response.associations[i].value);
+    }
+    if (!response.errorMessage.empty()) {
+        propValue->value.stringValue = response.errorMessage;
+    }
+    return propValue;
+}
+
+}  // namespace user_hal_helper
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/test/Android.bp b/automotive/vehicle/aidl/impl/fake_impl/userhal/test/Android.bp
new file mode 100644
index 0000000..7d0a534
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/test/Android.bp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "FakeUserHalTest",
+    vendor: true,
+    srcs: ["*.cpp"],
+    defaults: ["VehicleHalDefaults"],
+    static_libs: [
+        "FakeUserHal",
+        "VehicleHalUtils",
+        "libgtest",
+        "libgmock",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/automotive/vehicle/aidl/impl/fake_impl/userhal/test/UserHalHelper_test.cpp b/automotive/vehicle/aidl/impl/fake_impl/userhal/test/UserHalHelper_test.cpp
new file mode 100644
index 0000000..2afb2e3
--- /dev/null
+++ b/automotive/vehicle/aidl/impl/fake_impl/userhal/test/UserHalHelper_test.cpp
@@ -0,0 +1,715 @@
+/*
+ * 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 "UserHalHelper.h"
+
+#include <VehicleUtils.h>
+#include <aidl/android/hardware/automotive/vehicle/InitialUserInfoResponseAction.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace fake {
+namespace user_hal_helper {
+
+namespace {
+
+using ::testing::Eq;
+using ::testing::Gt;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::Pointee;
+
+using ::aidl::android::hardware::automotive::vehicle::CreateUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::CreateUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::CreateUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoRequest;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoRequestType;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponse;
+using ::aidl::android::hardware::automotive::vehicle::InitialUserInfoResponseAction;
+using ::aidl::android::hardware::automotive::vehicle::RemoveUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserMessageType;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserRequest;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserResponse;
+using ::aidl::android::hardware::automotive::vehicle::SwitchUserStatus;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationAssociationSetValue;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationAssociationType;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationAssociationValue;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationGetRequest;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationResponse;
+using ::aidl::android::hardware::automotive::vehicle::UserIdentificationSetRequest;
+using ::aidl::android::hardware::automotive::vehicle::UserInfo;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+
+constexpr int32_t INITIAL_USER_INFO = toInt(VehicleProperty::INITIAL_USER_INFO);
+constexpr int32_t SWITCH_USER = toInt(VehicleProperty::SWITCH_USER);
+constexpr int32_t CREATE_USER = toInt(VehicleProperty::CREATE_USER);
+constexpr int32_t REMOVE_USER = toInt(VehicleProperty::REMOVE_USER);
+constexpr int32_t USER_IDENTIFICATION_ASSOCIATION =
+        toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION);
+
+constexpr int32_t FIRST_BOOT_AFTER_OTA = toInt(InitialUserInfoRequestType::FIRST_BOOT_AFTER_OTA);
+constexpr int32_t LEGACY_ANDROID_SWITCH = toInt(SwitchUserMessageType::LEGACY_ANDROID_SWITCH);
+constexpr int32_t VEHICLE_REQUEST = toInt(SwitchUserMessageType::VEHICLE_REQUEST);
+
+constexpr int32_t NONE_USER = 0;
+constexpr int32_t GUEST_USER = toInt(UserInfo::USER_FLAG_GUEST);
+constexpr int32_t SYSTEM_USER = toInt(UserInfo::USER_FLAG_SYSTEM);
+constexpr int32_t SYSTEM_ADMIN_USER = SYSTEM_USER | toInt(UserInfo::USER_FLAG_ADMIN);
+// 0x1111 is not a valid UserFlags combination.
+constexpr int32_t INVALID_USER_FLAG = 0x1111;
+
+constexpr int32_t USER_ID_ASSOC_KEY_FOB = toInt(UserIdentificationAssociationType::KEY_FOB);
+constexpr int32_t USER_ID_ASSOC_CUSTOM_1 = toInt(UserIdentificationAssociationType::CUSTOM_1);
+
+constexpr int32_t USER_ID_ASSOC_SET_CURRENT_USER =
+        toInt(UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER);
+constexpr int32_t USER_ID_ASSOC_UNSET_CURRENT_USER =
+        toInt(UserIdentificationAssociationSetValue::DISASSOCIATE_CURRENT_USER);
+
+constexpr int32_t USER_ID_ASSOC_CURRENT_USER =
+        toInt(UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER);
+constexpr int32_t USER_ID_ASSOC_NO_USER =
+        toInt(UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER);
+
+}  // namespace
+
+TEST(UserHalHelperTest, TestToInitialUserInfoRequest) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER,
+                                      10, NONE_USER}},
+    };
+    InitialUserInfoRequest expected{
+            .requestId = 23,
+            .requestType = InitialUserInfoRequestType::FIRST_BOOT_AFTER_OTA,
+            .usersInfo = {{10, 0}, 2, {{0, SYSTEM_USER}, {10, 0}}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestToInitialUserInfoRequestUserFlagsBitCombination) {
+    // SYSTEM_ADMIN_USER is two UserFlags combined.
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0,
+                                      SYSTEM_ADMIN_USER, 10, NONE_USER}},
+    };
+    InitialUserInfoRequest expected{
+            .requestId = 23,
+            .requestType = InitialUserInfoRequestType::FIRST_BOOT_AFTER_OTA,
+            .usersInfo = {{10, 0}, 2, {{0, SYSTEM_ADMIN_USER}, {10, 0}}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestToInitialUserInfoRequestUserInvalidUserFlag) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0,
+                                      INVALID_USER_FLAG, 10, NONE_USER}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid user flags";
+}
+
+TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INT32_MAX,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER,
+                                      10, NONE_USER}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithInvalidRequestType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, INT32_MAX, 10, NONE_USER, 2, 0, SYSTEM_USER, 10,
+                                      NONE_USER}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid request type";
+}
+
+TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithInvalidUserFlag) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER,
+                                      10, INT32_MAX}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid user flags";
+}
+
+TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithIncompleteUsersInfo) {
+    VehiclePropValue propValueMissingSecondUserInfo{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0,
+                                      SYSTEM_USER /*Missing 2nd UserInfo*/}},
+    };
+
+    auto actual = toInitialUserInfoRequest(propValueMissingSecondUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info";
+
+    VehiclePropValue propValueMissingUsersInfo{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, /*Missing UsersInfo*/}},
+    };
+
+    actual = toInitialUserInfoRequest(propValueMissingUsersInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing users info";
+}
+
+TEST(UserHalHelperTest, TestToSwitchUserRequest) {
+    VehiclePropValue propValue{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2,
+                                      0, SYSTEM_USER, 10, NONE_USER}},
+    };
+    SwitchUserRequest expected{
+            .requestId = 23,
+            .messageType = SwitchUserMessageType::LEGACY_ANDROID_SWITCH,
+            .targetUser = {0, SYSTEM_USER},
+            .usersInfo = {{10, 0}, 2, {{0, SYSTEM_USER}, {10, 0}}},
+    };
+
+    auto actual = toSwitchUserRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2,
+                                      0, SYSTEM_USER, 10, NONE_USER}},
+    };
+
+    auto actual = toSwitchUserRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithInvalidMessageType) {
+    VehiclePropValue propValueIncompatibleMessageType{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, VEHICLE_REQUEST, 0, SYSTEM_USER, 10, NONE_USER, 2, 0,
+                                      SYSTEM_USER, 10, NONE_USER}},
+    };
+
+    auto actual = toSwitchUserRequest(propValueIncompatibleMessageType);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on incompatible message type";
+
+    VehiclePropValue propValueInvalidMessageType{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, INT32_MAX, 0, SYSTEM_USER, 10, NONE_USER, 2, 0,
+                                      SYSTEM_USER, 10, NONE_USER}},
+    };
+
+    actual = toSwitchUserRequest(propValueInvalidMessageType);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid message type";
+}
+
+TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithIncompleteUsersInfo) {
+    VehiclePropValue propValueMissingSecondUserInfo{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2,
+                                      0, SYSTEM_USER,
+                                      /*Missing 2nd UserInfo*/}},
+    };
+
+    auto actual = toSwitchUserRequest(propValueMissingSecondUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info";
+
+    VehiclePropValue propValueMissingUsersInfo{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER,
+                                      /*Missing UsersInfo*/}},
+    };
+
+    actual = toSwitchUserRequest(propValueMissingUsersInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing users info";
+
+    VehiclePropValue propValueMissingTargetUser{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, /*Missing target UserInfo*/}},
+    };
+
+    actual = toSwitchUserRequest(propValueMissingTargetUser);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing target user info";
+}
+
+TEST(UserHalHelperTest, TestToCreateUserRequest) {
+    VehiclePropValue propValue{
+            .prop = CREATE_USER,
+            .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10,
+                                      NONE_USER},
+                      .stringValue = "Guest11"},
+    };
+    CreateUserRequest expected{
+            .requestId = 23,
+            .newUserInfo = {11, GUEST_USER},
+            .newUserName = "Guest11",
+            .usersInfo = {{10, 0}, 2, {{0, SYSTEM_USER}, {10, 0}}},
+    };
+
+    auto actual = toCreateUserRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestFailsToCreateUserRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10,
+                                      NONE_USER},
+                      .stringValue = "Guest11"},
+    };
+
+    auto actual = toCreateUserRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToCreateUserRequestWithIncompleteUsersInfo) {
+    VehiclePropValue propValueMissingSecondUserInfo{
+            .prop = CREATE_USER,
+            .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0,
+                                      SYSTEM_USER /*Missing 2nd UserInfo*/},
+                      .stringValue = "Guest11"},
+    };
+
+    auto actual = toCreateUserRequest(propValueMissingSecondUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info";
+
+    VehiclePropValue propValueMissingUsersInfo{
+            .prop = CREATE_USER,
+            .value = {.int32Values = {23, 11, GUEST_USER, /*Missing UsersInfo*/},
+                      .stringValue = "Guest11"},
+    };
+
+    actual = toCreateUserRequest(propValueMissingUsersInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing users info";
+
+    VehiclePropValue propValueMissingCreateUserInfo{
+            .prop = CREATE_USER,
+            .value = {.int32Values = {23, /*Missing create UserInfo*/}, .stringValue = "Guest11"},
+    };
+
+    actual = toCreateUserRequest(propValueMissingCreateUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing create user info";
+}
+
+TEST(UserHalHelperTest, TestToRemoveUserRequest) {
+    VehiclePropValue propValue{
+            .prop = REMOVE_USER,
+            .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10,
+                                      NONE_USER}},
+    };
+    RemoveUserRequest expected{
+            .requestId = 23,
+            .removedUserInfo = {10, 0},
+            .usersInfo = {{10, 0}, 2, {{0, SYSTEM_USER}, {10, 0}}},
+    };
+
+    auto actual = toRemoveUserRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestFailsToRemoveUserRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10,
+                                      NONE_USER}},
+    };
+
+    auto actual = toRemoveUserRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToRemoveUserRequestWithIncompleteUsersInfo) {
+    VehiclePropValue propValueMissingSecondUserInfo{
+            .prop = REMOVE_USER,
+            .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0,
+                                      SYSTEM_USER /*Missing 2nd UserInfo*/}},
+    };
+
+    auto actual = toRemoveUserRequest(propValueMissingSecondUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info";
+
+    VehiclePropValue propValueMissingUsersInfo{
+            .prop = REMOVE_USER,
+            .value = {.int32Values = {23, 10, NONE_USER, /*Missing UsersInfo*/}},
+    };
+
+    actual = toRemoveUserRequest(propValueMissingUsersInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing users info";
+
+    VehiclePropValue propValueMissingRemoveUserInfo{
+            .prop = REMOVE_USER,
+            .value = {.int32Values = {23, /*Missing remove UserInfo*/}},
+    };
+
+    actual = toRemoveUserRequest(propValueMissingRemoveUserInfo);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing remove user info";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequest) {
+    VehiclePropValue propValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB,
+                                      USER_ID_ASSOC_CUSTOM_1}},
+    };
+    UserIdentificationGetRequest expected{
+            .requestId = 23,
+            .userInfo = {10, 0},
+            .numberAssociationTypes = 2,
+            .associationTypes = {UserIdentificationAssociationType::KEY_FOB,
+                                 UserIdentificationAssociationType::CUSTOM_1},
+    };
+
+    auto actual = toUserIdentificationGetRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB,
+                                      USER_ID_ASSOC_CUSTOM_1}},
+    };
+
+    auto actual = toUserIdentificationGetRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithInvalidAssociationTypes) {
+    VehiclePropValue propValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 1, INT32_MAX}},
+    };
+
+    auto actual = toUserIdentificationGetRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid association type";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithIncompleteAssociationTypes) {
+    VehiclePropValue propValueMissingSecondAssociationType{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 2,
+                                      USER_ID_ASSOC_KEY_FOB /*Missing 2nd association type*/}},
+    };
+
+    auto actual = toUserIdentificationGetRequest(propValueMissingSecondAssociationType);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second association type";
+
+    VehiclePropValue propValueMissingNumberAssociationTypes{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, /*Missing number association types*/}},
+    };
+
+    actual = toUserIdentificationGetRequest(propValueMissingNumberAssociationTypes);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithMissingUserInfo) {
+    VehiclePropValue propValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, /*Missing user info*/}},
+    };
+
+    auto actual = toUserIdentificationGetRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing UserInfo";
+}
+
+TEST(UserHalHelperTest, TestToUserIdentificationSetRequest) {
+    VehiclePropValue propValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB,
+                                      USER_ID_ASSOC_SET_CURRENT_USER, USER_ID_ASSOC_CUSTOM_1,
+                                      USER_ID_ASSOC_UNSET_CURRENT_USER}},
+    };
+    UserIdentificationSetRequest expected{
+            .requestId = 23,
+            .userInfo = {10, 0},
+            .numberAssociations = 2,
+            .associations = {{UserIdentificationAssociationType::KEY_FOB,
+                              UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER},
+                             {UserIdentificationAssociationType::CUSTOM_1,
+                              UserIdentificationAssociationSetValue::DISASSOCIATE_CURRENT_USER}},
+    };
+
+    auto actual = toUserIdentificationSetRequest(propValue);
+
+    ASSERT_TRUE(actual.ok()) << actual.error().message();
+    EXPECT_THAT(actual.value(), Eq(expected));
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithMismatchingPropType) {
+    VehiclePropValue propValue{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB,
+                                      USER_ID_ASSOC_SET_CURRENT_USER, USER_ID_ASSOC_CUSTOM_1,
+                                      USER_ID_ASSOC_UNSET_CURRENT_USER}},
+    };
+
+    auto actual = toUserIdentificationSetRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithInvalidAssociations) {
+    VehiclePropValue propValueInvalidAssociationType{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 1, INT32_MAX,
+                                      USER_ID_ASSOC_SET_CURRENT_USER}},
+    };
+
+    auto actual = toUserIdentificationSetRequest(propValueInvalidAssociationType);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on invalid association type";
+
+    VehiclePropValue propValueInvalidAssociationValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, USER_ID_ASSOC_KEY_FOB, INT32_MAX}},
+    };
+
+    actual = toUserIdentificationSetRequest(propValueInvalidAssociationValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithIncompleteAssociations) {
+    VehiclePropValue propValueMissingSecondAssociationType{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB,
+                                      USER_ID_ASSOC_SET_CURRENT_USER,
+                                      /*Missing 2nd association*/}},
+    };
+
+    auto actual = toUserIdentificationSetRequest(propValueMissingSecondAssociationType);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing second association type";
+
+    VehiclePropValue propValueMissingNumberAssociationTypes{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 10, NONE_USER, /*Missing number associations*/}},
+    };
+
+    actual = toUserIdentificationSetRequest(propValueMissingNumberAssociationTypes);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types";
+}
+
+TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithMissingUserInfo) {
+    VehiclePropValue propValue{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, /*Missing user info*/}},
+    };
+
+    auto actual = toUserIdentificationSetRequest(propValue);
+
+    EXPECT_FALSE(actual.ok()) << "No error returned on missing UserInfo";
+}
+
+TEST(UserHalHelperTest, TestSwitchUserRequestToVehiclePropValue) {
+    SwitchUserRequest request{
+            .requestId = 23,
+            .messageType = SwitchUserMessageType::VEHICLE_REQUEST,
+            .targetUser = {11, GUEST_USER},
+    };
+    VehiclePropValue expected{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23,
+                                      static_cast<int32_t>(SwitchUserMessageType::VEHICLE_REQUEST),
+                                      11}},
+    };
+
+    VehiclePropValuePool pool;
+    auto result = toVehiclePropValue(pool, request);
+
+    ASSERT_TRUE(result.ok());
+    EXPECT_THAT(result.value()->timestamp, Gt(0));
+    // Don't rely on real timestamp in tests as the expected and actual objects won't have the same
+    // timestamps. Thus remove the timestamps before comparing them.
+    result.value()->timestamp = 0;
+    EXPECT_THAT(result.value(), Pointee(Eq(expected)));
+}
+
+TEST(UserHalHelperTest, TestFailsSwitchUserRequestToVehiclePropValueWithIncompatibleMessageType) {
+    SwitchUserRequest request{
+            .requestId = 23,
+            .messageType = SwitchUserMessageType::VEHICLE_RESPONSE,
+            .targetUser = {11, GUEST_USER},
+    };
+
+    VehiclePropValuePool pool;
+    auto result = toVehiclePropValue(pool, request);
+
+    EXPECT_FALSE(result.ok());
+}
+
+TEST(UserHalHelperTest, TestInitialUserInfoResponseToVehiclePropValue) {
+    InitialUserInfoResponse response{
+            .requestId = 23,
+            .action = InitialUserInfoResponseAction::CREATE,
+            .userToSwitchOrCreate = {11, GUEST_USER},
+            .userLocales = "en-US,pt-BR",
+            .userNameToCreate = "Owner",
+    };
+    VehiclePropValue expected{
+            .prop = INITIAL_USER_INFO,
+            .value = {.int32Values = {23,
+                                      static_cast<int32_t>(InitialUserInfoResponseAction::CREATE),
+                                      11, GUEST_USER},
+                      .stringValue = "en-US,pt-BR||Owner"},
+    };
+
+    VehiclePropValuePool pool;
+    auto actual = toVehiclePropValue(pool, response);
+
+    ASSERT_THAT(actual, NotNull());
+    EXPECT_THAT(actual->timestamp, Gt(0));
+    actual->timestamp = 0;
+    EXPECT_THAT(actual, Pointee(Eq(expected)));
+}
+
+TEST(UserHalHelperTest, TestSwitchUserResponseToVehiclePropValue) {
+    SwitchUserResponse response{
+            .requestId = 23,
+            .messageType = SwitchUserMessageType::VEHICLE_RESPONSE,
+            .status = SwitchUserStatus::FAILURE,
+            .errorMessage = "random error",
+    };
+    VehiclePropValue expected{
+            .prop = SWITCH_USER,
+            .value = {.int32Values = {23,
+                                      static_cast<int32_t>(SwitchUserMessageType::VEHICLE_RESPONSE),
+                                      static_cast<int32_t>(SwitchUserStatus::FAILURE)},
+                      .stringValue = "random error"},
+    };
+
+    VehiclePropValuePool pool;
+    auto actual = toVehiclePropValue(pool, response);
+
+    ASSERT_THAT(actual, NotNull());
+    EXPECT_THAT(actual->timestamp, Gt(0));
+    actual->timestamp = 0;
+    EXPECT_THAT(actual, Pointee(Eq(expected)));
+}
+
+TEST(UserHalHelperTest, TestCreateUserResponseToVehiclePropValue) {
+    CreateUserResponse response{
+            .requestId = 23,
+            .status = CreateUserStatus::FAILURE,
+            .errorMessage = "random error",
+    };
+    VehiclePropValue expected{
+            .prop = CREATE_USER,
+            .value = {.int32Values = {23, static_cast<int32_t>(CreateUserStatus::FAILURE)},
+                      .stringValue = "random error"},
+    };
+
+    VehiclePropValuePool pool;
+    auto actual = toVehiclePropValue(pool, response);
+
+    ASSERT_THAT(actual, NotNull());
+    EXPECT_THAT(actual->timestamp, Gt(0));
+    actual->timestamp = 0;
+    EXPECT_THAT(actual, Pointee(Eq(expected)));
+}
+
+TEST(UserHalHelperTest, TestUserIdentificationResponseToVehiclePropValue) {
+    UserIdentificationResponse response{
+            .requestId = 23,
+            .numberAssociation = 2,
+            .associations = {{UserIdentificationAssociationType::KEY_FOB,
+                              UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER},
+                             {UserIdentificationAssociationType::CUSTOM_1,
+                              UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER}},
+            .errorMessage = "random error",
+    };
+    VehiclePropValue expected{
+            .prop = USER_IDENTIFICATION_ASSOCIATION,
+            .value = {.int32Values = {23, 2, USER_ID_ASSOC_KEY_FOB, USER_ID_ASSOC_CURRENT_USER,
+                                      USER_ID_ASSOC_CUSTOM_1, USER_ID_ASSOC_NO_USER},
+                      .stringValue = "random error"},
+    };
+
+    VehiclePropValuePool pool;
+    auto actual = toVehiclePropValue(pool, response);
+
+    ASSERT_THAT(actual, NotNull());
+    EXPECT_THAT(actual->timestamp, Gt(0));
+    actual->timestamp = 0;
+    EXPECT_THAT(actual, Pointee(Eq(expected)));
+}
+
+}  // namespace user_hal_helper
+}  // namespace fake
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h b/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h
index 441c54b..2743578 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h
@@ -107,7 +107,7 @@
 constexpr int HVAC_RIGHT = SEAT_1_RIGHT | SEAT_2_RIGHT;
 constexpr int HVAC_ALL = HVAC_LEFT | HVAC_RIGHT;
 
-const int32_t kHvacPowerProperties[] = {
+const int32_t HVAC_POWER_PROPERTIES[] = {
         toInt(propertyutils_impl::VehicleProperty::HVAC_FAN_SPEED),
         toInt(propertyutils_impl::VehicleProperty::HVAC_FAN_DIRECTION),
 };
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
index 9da2b14..2b36c72 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleHalTypes.h
@@ -25,6 +25,12 @@
 #include <aidl/android/hardware/automotive/vehicle/FuelType.h>
 #include <aidl/android/hardware/automotive/vehicle/GetValueRequest.h>
 #include <aidl/android/hardware/automotive/vehicle/GetValueResult.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>
+#include <aidl/android/hardware/automotive/vehicle/Obd2IgnitionMonitorKind.h>
+#include <aidl/android/hardware/automotive/vehicle/Obd2SecondaryAirStatus.h>
+#include <aidl/android/hardware/automotive/vehicle/Obd2SparkIgnitionMonitors.h>
 #include <aidl/android/hardware/automotive/vehicle/PortLocationType.h>
 #include <aidl/android/hardware/automotive/vehicle/SetValueRequest.h>
 #include <aidl/android/hardware/automotive/vehicle/SetValueResult.h>
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
index dcc7031..63129e7 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
@@ -110,6 +110,8 @@
     // Set a callback that would be called when a property value has been updated.
     void setOnValueChangeCallback(const OnValueChangeCallback& callback);
 
+    inline std::shared_ptr<VehiclePropValuePool> getValuePool() { return mValuePool; }
+
   private:
     struct RecordId {
         int32_t area;
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
index b02aaf7..95e58c6 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
@@ -130,10 +130,9 @@
         const ::aidl::android::hardware::automotive::vehicle::RawPropValues& value,
         ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType type) {
     switch (type) {
-        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::INT32:  // fall
-                                                                                          // through
-        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::
-                BOOLEAN:  // fall through
+        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::INT32:
+            [[fallthrough]];
+        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::BOOLEAN:
             return std::min(value.int32Values.size(), static_cast<size_t>(1));
         case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::FLOAT:
             return std::min(value.floatValues.size(), static_cast<size_t>(1));
@@ -147,6 +146,10 @@
             return value.int64Values.size();
         case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::BYTES:
             return value.byteValues.size();
+        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::STRING:
+            [[fallthrough]];
+        case ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType::MIXED:
+            return 0;
         default:
             ALOGE("getVehicleRawValueVectorSize: unknown type: %d", toInt(type));
             return 0;
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/VehicleObjectPool.cpp b/automotive/vehicle/aidl/impl/utils/common/src/VehicleObjectPool.cpp
index 1cdc461..2480a73 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/VehicleObjectPool.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/VehicleObjectPool.cpp
@@ -52,17 +52,19 @@
 }
 
 VehiclePropValuePool::RecyclableType VehiclePropValuePool::obtain(const VehiclePropValue& src) {
-    VehiclePropertyType type = getPropType(src.prop);
+    int propId = src.prop;
+    VehiclePropertyType type = getPropType(propId);
     size_t vectorSize = getVehicleRawValueVectorSize(src.value, type);
     if (vectorSize == 0 && !isComplexType(type)) {
         ALOGW("empty vehicle prop value, contains no content");
+        ALOGW("empty vehicle prop value, contains no content, prop: %d", propId);
         // Return any empty VehiclePropValue.
-        return RecyclableType{new VehiclePropValue, mDisposableDeleter};
+        return RecyclableType{new VehiclePropValue{}, mDisposableDeleter};
     }
 
     auto dest = obtain(type, vectorSize);
 
-    dest->prop = src.prop;
+    dest->prop = propId;
     dest->areaId = src.areaId;
     dest->status = src.status;
     dest->timestamp = src.timestamp;
diff --git a/automotive/vehicle/aidl/impl/utils/test/Android.bp b/automotive/vehicle/aidl/impl/utils/test/Android.bp
index 5859151..ad9954f 100644
--- a/automotive/vehicle/aidl/impl/utils/test/Android.bp
+++ b/automotive/vehicle/aidl/impl/utils/test/Android.bp
@@ -20,6 +20,7 @@
 
 cc_library_headers {
     name: "VehicleHalTestUtilHeaders",
-    export_include_dirs: ["include"],
     vendor: true,
+    header_libs: ["VehicleHalUtilHeaders"],
+    export_include_dirs: ["include"],
 }
diff --git a/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h b/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h
index 77cf100..f80d1e6 100644
--- a/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h
@@ -29,6 +29,7 @@
 
 // These names are not part of the API since we only expose ints.
 using ::aidl::android::hardware::automotive::vehicle::VehicleArea;
+using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyGroup;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
 
@@ -44,16 +45,16 @@
 }
 
 // These properties are used for the end-to-end testing of ClusterHomeService.
-constexpr int32_t VENDOR_CLUSTER_SWITCH_UI = toVendor(
-        ::aidl::android::hardware::automotive::vehicle::VehicleProperty::CLUSTER_SWITCH_UI);
+constexpr int32_t VENDOR_CLUSTER_SWITCH_UI =
+        toVendor(testpropertyutils_impl::VehicleProperty::CLUSTER_SWITCH_UI);
 constexpr int32_t VENDOR_CLUSTER_DISPLAY_STATE =
-        toVendor(::aidl::hardware::automotive::vehicle::VehicleProperty::CLUSTER_DISPLAY_STATE);
+        toVendor(testpropertyutils_impl::VehicleProperty::CLUSTER_DISPLAY_STATE);
 constexpr int32_t VENDOR_CLUSTER_REPORT_STATE =
-        toVendor(::aidl::hardware::automotive::vehicle::VehicleProperty::CLUSTER_REPORT_STATE);
+        toVendor(testpropertyutils_impl::VehicleProperty::CLUSTER_REPORT_STATE);
 constexpr int32_t VENDOR_CLUSTER_REQUEST_DISPLAY =
-        toVendor(::aidl::hardware::automotive::vehicle::VehicleProperty::CLUSTER_REQUEST_DISPLAY);
+        toVendor(testpropertyutils_impl::VehicleProperty::CLUSTER_REQUEST_DISPLAY);
 constexpr int32_t VENDOR_CLUSTER_NAVIGATION_STATE =
-        toVendor(::aidl::hardware::automotive::vehicle::VehicleProperty::CLUSTER_NAVIGATION_STATE);
+        toVendor(testpropertyutils_impl::VehicleProperty::CLUSTER_NAVIGATION_STATE);
 #endif  // ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
 
 // These properties are placeholder properties for developers to test new features without
diff --git a/biometrics/fingerprint/aidl/Android.bp b/biometrics/fingerprint/aidl/Android.bp
index 5295098..c46150e 100644
--- a/biometrics/fingerprint/aidl/Android.bp
+++ b/biometrics/fingerprint/aidl/Android.bp
@@ -26,5 +26,8 @@
             enabled: false,
         },
     },
-    versions: ["1"],
+    versions: [
+        "1",
+        "2",
+    ],
 }
diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/.hash b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/.hash
new file mode 100644
index 0000000..411cb75
--- /dev/null
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/.hash
@@ -0,0 +1 @@
+762eb38ce93ea3c7d39a680949cbdbd2371b3f06
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl
similarity index 80%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl
index 2b872ab..c51aa03 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/AcquiredInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,20 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@Backing(type="byte") @VintfStability
+enum AcquiredInfo {
+  UNKNOWN = 0,
+  GOOD = 1,
+  PARTIAL = 2,
+  INSUFFICIENT = 3,
+  SENSOR_DIRTY = 4,
+  TOO_SLOW = 5,
+  TOO_FAST = 6,
+  VENDOR = 7,
+  START = 8,
+  TOO_DARK = 9,
+  TOO_BRIGHT = 10,
+  IMMOBILE = 11,
+  RETRYING_CAPTURE = 12,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/Error.aidl
similarity index 82%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/Error.aidl
index 2b872ab..af7bc3c 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/Error.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,16 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@Backing(type="byte") @VintfStability
+enum Error {
+  UNKNOWN = 0,
+  HW_UNAVAILABLE = 1,
+  UNABLE_TO_PROCESS = 2,
+  TIMEOUT = 3,
+  NO_SPACE = 4,
+  CANCELED = 5,
+  UNABLE_TO_REMOVE = 6,
+  VENDOR = 7,
+  BAD_CALIBRATION = 8,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl
similarity index 84%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl
index 2b872ab..9c208c4 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/FingerprintSensorType.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,13 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@Backing(type="byte") @VintfStability
+enum FingerprintSensorType {
+  UNKNOWN = 0,
+  REAR = 1,
+  UNDER_DISPLAY_ULTRASONIC = 2,
+  UNDER_DISPLAY_OPTICAL = 3,
+  POWER_BUTTON = 4,
+  HOME_BUTTON = 5,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/IFingerprint.aidl
similarity index 80%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/IFingerprint.aidl
index 2b872ab..5d3df6f 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/IFingerprint.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,9 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@VintfStability
+interface IFingerprint {
+  android.hardware.biometrics.fingerprint.SensorProps[] getSensorProps();
+  android.hardware.biometrics.fingerprint.ISession createSession(in int sensorId, in int userId, in android.hardware.biometrics.fingerprint.ISessionCallback cb);
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISession.aidl
similarity index 64%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISession.aidl
index 2b872ab..9934a76 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISession.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,21 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@VintfStability
+interface ISession {
+  void generateChallenge();
+  void revokeChallenge(in long challenge);
+  android.hardware.biometrics.common.ICancellationSignal enroll(in android.hardware.keymaster.HardwareAuthToken hat);
+  android.hardware.biometrics.common.ICancellationSignal authenticate(in long operationId);
+  android.hardware.biometrics.common.ICancellationSignal detectInteraction();
+  void enumerateEnrollments();
+  void removeEnrollments(in int[] enrollmentIds);
+  void getAuthenticatorId();
+  void invalidateAuthenticatorId();
+  void resetLockout(in android.hardware.keymaster.HardwareAuthToken hat);
+  void close();
+  void onPointerDown(in int pointerId, in int x, in int y, in float minor, in float major);
+  void onPointerUp(in int pointerId);
+  void onUiReady();
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
similarity index 61%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
index 2b872ab..3c40ad6 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/ISessionCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,23 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@VintfStability
+interface ISessionCallback {
+  void onChallengeGenerated(in long challenge);
+  void onChallengeRevoked(in long challenge);
+  void onAcquired(in android.hardware.biometrics.fingerprint.AcquiredInfo info, in int vendorCode);
+  void onError(in android.hardware.biometrics.fingerprint.Error error, in int vendorCode);
+  void onEnrollmentProgress(in int enrollmentId, int remaining);
+  void onAuthenticationSucceeded(in int enrollmentId, in android.hardware.keymaster.HardwareAuthToken hat);
+  void onAuthenticationFailed();
+  void onLockoutTimed(in long durationMillis);
+  void onLockoutPermanent();
+  void onLockoutCleared();
+  void onInteractionDetected();
+  void onEnrollmentsEnumerated(in int[] enrollmentIds);
+  void onEnrollmentsRemoved(in int[] enrollmentIds);
+  void onAuthenticatorIdRetrieved(in long authenticatorId);
+  void onAuthenticatorIdInvalidated(in long newAuthenticatorId);
+  void onSessionClosed();
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorLocation.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorLocation.aidl
index 2b872ab..295fde9 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorLocation.aidl
@@ -31,14 +31,12 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@VintfStability
+parcelable SensorLocation {
+  int displayId;
+  int sensorLocationX;
+  int sensorLocationY;
+  int sensorRadius;
+  String display = "";
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorProps.aidl
similarity index 73%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorProps.aidl
index 2b872ab..782d289 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/2/android/hardware/biometrics/fingerprint/SensorProps.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -31,14 +31,14 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.biometrics.fingerprint;
+@VintfStability
+parcelable SensorProps {
+  android.hardware.biometrics.common.CommonProps commonProps;
+  android.hardware.biometrics.fingerprint.FingerprintSensorType sensorType = android.hardware.biometrics.fingerprint.FingerprintSensorType.UNKNOWN;
+  android.hardware.biometrics.fingerprint.SensorLocation[] sensorLocations;
+  boolean supportsNavigationGestures;
+  boolean supportsDetectInteraction;
+  boolean halHandlesDisplayTouches;
+  boolean halControlsIllumination;
 }
diff --git a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl
index 515ddaa..295fde9 100644
--- a/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl
+++ b/biometrics/fingerprint/aidl/aidl_api/android.hardware.biometrics.fingerprint/current/android/hardware/biometrics/fingerprint/SensorLocation.aidl
@@ -38,4 +38,5 @@
   int sensorLocationX;
   int sensorLocationY;
   int sensorRadius;
+  String display = "";
 }
diff --git a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl
index b1618b2..d12605c 100644
--- a/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl
+++ b/biometrics/fingerprint/aidl/android/hardware/biometrics/fingerprint/SensorLocation.aidl
@@ -19,15 +19,7 @@
 @VintfStability
 parcelable SensorLocation {
     /**
-     * The display to which the following measurements are relative to. This must correspond to the
-     * android.hardware.DisplayManager#getDisplay Android API.
-     *
-     * A few examples:
-     *   1) A capacitive rear fingerprint sensor would specify the display to which it is behind.
-     *   2) An under-display fingerprint sensor would specify the display on which the sensor is
-     *      located.
-     *   3) A foldable device would specify multiple locations and have a SensorLocation entry
-     *      for each display from which the sensor is accessible from.
+     * Deprecated use the display field instead. This field was never used.
      */
     int displayId;
 
@@ -51,4 +43,18 @@
      * in pixels.
      */
     int sensorRadius;
+
+    /**
+     * The display to which all of the measurements are relative to. This must correspond to the
+     * android.view.Display#getUniqueId Android API. The default display is used if this field is
+     * empty.
+     *
+     * A few examples:
+     *   1) A capacitive rear fingerprint sensor would specify the display to which it is behind.
+     *   2) An under-display fingerprint sensor would specify the display on which the sensor is
+     *      located.
+     *   3) A foldable device would specify multiple locations and have a SensorLocation entry
+     *      for each display from which the sensor is accessible from.
+     */
+    String display = "";
 }
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 08fe4b0..d4194a3 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -24,7 +24,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.biometrics.fingerprint-V1-ndk",
+        "android.hardware.biometrics.fingerprint-V2-ndk",
         "android.hardware.biometrics.common-V1-ndk",
     ],
 }
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index fbfa52f..1f14de6 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -47,8 +47,9 @@
     common::CommonProps commonProps = {SENSOR_ID, SENSOR_STRENGTH, MAX_ENROLLMENTS_PER_USER,
                                        componentInfo};
 
-    SensorLocation sensorLocation = {0 /* displayId */, 0 /* sensorLocationX */,
-                                     0 /* sensorLocationY */, 0 /* sensorRadius */};
+    SensorLocation sensorLocation = {0 /* displayId (not used) */, 0 /* sensorLocationX */,
+                                     0 /* sensorLocationY */, 0 /* sensorRadius */,
+                                     "" /* display */};
 
     *out = {{commonProps,
              SENSOR_TYPE,
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-default.xml b/biometrics/fingerprint/aidl/default/fingerprint-default.xml
index 89da765..d322eb6 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint-default.xml
+++ b/biometrics/fingerprint/aidl/default/fingerprint-default.xml
@@ -1,6 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.biometrics.fingerprint</name>
+        <version>2</version>
         <fqname>IFingerprint/default</fqname>
     </hal>
 </manifest>
diff --git a/bluetooth/1.0/default/h4_protocol.cc b/bluetooth/1.0/default/h4_protocol.cc
index 43abbe4..33238da 100644
--- a/bluetooth/1.0/default/h4_protocol.cc
+++ b/bluetooth/1.0/default/h4_protocol.cc
@@ -30,21 +30,52 @@
 namespace hci {
 
 size_t H4Protocol::Send(uint8_t type, const uint8_t* data, size_t length) {
-  struct iovec iov[] = {{&type, sizeof(type)},
-                        {const_cast<uint8_t*>(data), length}};
-  ssize_t ret = 0;
-  do {
-    ret =
-        TEMP_FAILURE_RETRY(writev(uart_fd_, iov, sizeof(iov) / sizeof(iov[0])));
-  } while (-1 == ret && EAGAIN == errno);
-
-  if (ret == -1) {
-    ALOGE("%s error writing to UART (%s)", __func__, strerror(errno));
-  } else if (ret < static_cast<ssize_t>(length + 1)) {
-    ALOGE("%s: %d / %d bytes written - something went wrong...", __func__,
-          static_cast<int>(ret), static_cast<int>(length + 1));
+  struct iovec iov_array[] = {{&type, sizeof(type)},
+                              {const_cast<uint8_t*>(data), length}};
+  struct iovec* iov = iov_array;
+  int iovcnt = sizeof(iov_array) / sizeof(iov_array[0]);
+  size_t total_bytes = 0;
+  for (int i = 0; i < iovcnt; i++) {
+    total_bytes += iov_array[i].iov_len;
   }
-  return ret;
+  size_t bytes_written = 0;
+  size_t remaining_bytes = total_bytes;
+
+  while (remaining_bytes > 0) {
+    ssize_t ret = TEMP_FAILURE_RETRY(writev(uart_fd_, iov, iovcnt));
+    if (ret == -1) {
+      if (errno == EAGAIN) continue;
+      ALOGE("%s error writing to UART (%s)", __func__, strerror(errno));
+      break;
+    } else if (ret == 0) {
+      // Nothing written
+      ALOGE("%s zero bytes written - something went wrong...", __func__);
+      break;
+    } else if (ret == remaining_bytes) {
+      // Everything written
+      bytes_written += ret;
+      break;
+    }
+
+    bytes_written += ret;
+    remaining_bytes -= ret;
+    ALOGW("%s: %d/%d bytes written - retrying remaining %d bytes", __func__,
+          static_cast<int>(bytes_written), static_cast<int>(total_bytes),
+          static_cast<int>(remaining_bytes));
+
+    // Remove iovs which are written from the list
+    while (ret >= iov->iov_len) {
+      ret -= iov->iov_len;
+      ++iov;
+      --iovcnt;
+    }
+    // Adjust the iov to point to the remaining data which needs to be written
+    if (ret) {
+      iov->iov_base = static_cast<uint8_t*>(iov->iov_base) + ret;
+      iov->iov_len -= ret;
+    }
+  }
+  return bytes_written;
 }
 
 void H4Protocol::OnPacketReady() {
diff --git a/bluetooth/audio/2.2/Android.bp b/bluetooth/audio/2.2/Android.bp
new file mode 100644
index 0000000..6449c08
--- /dev/null
+++ b/bluetooth/audio/2.2/Android.bp
@@ -0,0 +1,32 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+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"],
+}
+
+hidl_interface {
+    name: "android.hardware.bluetooth.audio@2.2",
+    root: "android.hardware",
+    srcs: [
+        "types.hal",
+        "IBluetoothAudioProvider.hal",
+        "IBluetoothAudioProvidersFactory.hal",
+    ],
+    interfaces: [
+        "android.hardware.audio.common@5.0",
+        "android.hardware.bluetooth.audio@2.0",
+        "android.hardware.bluetooth.audio@2.1",
+        "android.hidl.base@1.0",
+        "android.hidl.safe_union@1.0",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.bluetooth.updatable",
+    ],
+    gen_java: false,
+}
diff --git a/bluetooth/audio/2.2/IBluetoothAudioProvider.hal b/bluetooth/audio/2.2/IBluetoothAudioProvider.hal
new file mode 100644
index 0000000..ad8c839
--- /dev/null
+++ b/bluetooth/audio/2.2/IBluetoothAudioProvider.hal
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.bluetooth.audio@2.2;
+
+import @2.1::IBluetoothAudioProvider;
+import @2.0::IBluetoothAudioPort;
+import @2.0::Status;
+
+/**
+ * HAL interface from the Bluetooth stack to the Audio HAL
+ *
+ * The Bluetooth stack calls methods in this interface to start and end audio
+ * sessions and sends callback events to the Audio HAL.
+ *
+ * Note: For HIDL APIs with a "generates" statement, the callback parameter used
+ * for return value must be invoked synchronously before the API call returns.
+ */
+interface IBluetoothAudioProvider extends @2.1::IBluetoothAudioProvider {
+
+    /**
+     * This method indicates that the Bluetooth stack is ready to stream audio.
+     * It registers an instance of IBluetoothAudioPort with and provides the
+     * current negotiated codec to the Audio HAL. After this method is called,
+     * the Audio HAL can invoke IBluetoothAudioPort.startStream().
+     *
+     * Note: endSession() must be called to unregister this IBluetoothAudioPort
+     *
+     * @param hostIf An instance of IBluetoothAudioPort for stream control
+     * @param audioConfig The audio configuration negotiated with the remote
+     *    device. The PCM parameters are set if software based encoding,
+     *    otherwise the correct codec configuration is used for hardware
+     *    encoding.
+     *
+     * @return status One of the following
+     *    SUCCESS if this IBluetoothAudioPort was successfully registered with
+     *        the Audio HAL
+     *    UNSUPPORTED_CODEC_CONFIGURATION if the Audio HAL cannot register this
+     *        IBluetoothAudioPort with the given codec configuration
+     *    FAILURE if the Audio HAL cannot register this IBluetoothAudioPort for
+     *        any other reason
+     * @return dataMQ The fast message queue for audio data from/to this
+     *    provider. Audio data will be in PCM format as specified by the
+     *    audioConfig.pcmConfig parameter. Invalid if streaming is offloaded
+     *    from/to hardware or on failure.
+     */
+    startSession_2_2(IBluetoothAudioPort hostIf, AudioConfiguration audioConfig)
+                generates (Status status, fmq_sync<uint8_t> dataMQ);
+};
diff --git a/bluetooth/audio/2.2/IBluetoothAudioProvidersFactory.hal b/bluetooth/audio/2.2/IBluetoothAudioProvidersFactory.hal
new file mode 100644
index 0000000..eeff4de
--- /dev/null
+++ b/bluetooth/audio/2.2/IBluetoothAudioProvidersFactory.hal
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.bluetooth.audio@2.2;
+
+import @2.1::IBluetoothAudioProvidersFactory;
+
+/**
+ * This factory allows a HAL implementation to be split into multiple
+ * independent providers.
+ *
+ * When the Bluetooth stack is ready to create an audio session, it must first
+ * obtain the IBluetoothAudioProvider for that session type by calling
+ * openProvider().
+ *
+ * Note: For HIDL APIs with a "generates" statement, the callback parameter used
+ * for return value must be invoked synchronously before the API call returns.
+ */
+interface IBluetoothAudioProvidersFactory extends @2.1::IBluetoothAudioProvidersFactory {
+};
diff --git a/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.cpp b/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.cpp
new file mode 100644
index 0000000..126bc9e
--- /dev/null
+++ b/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderA2dpOffload"
+
+#include "A2dpOffloadAudioProvider.h"
+
+#include <android-base/logging.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+A2dpOffloadAudioProvider::A2dpOffloadAudioProvider()
+    : BluetoothAudioProvider() {
+  session_type_ = V2_1::SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH;
+}
+
+bool A2dpOffloadAudioProvider::isValid(const V2_0::SessionType& sessionType) {
+  return isValid(static_cast<V2_1::SessionType>(sessionType));
+}
+
+bool A2dpOffloadAudioProvider::isValid(const V2_1::SessionType& sessionType) {
+  return (sessionType == session_type_);
+}
+
+Return<void> A2dpOffloadAudioProvider::startSession(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  if (audioConfig.getDiscriminator() !=
+      AudioConfiguration::hidl_discriminator::codecConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  } else if (!android::bluetooth::audio::IsOffloadCodecConfigurationValid(
+                 session_type_, audioConfig.codecConfig())) {
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  return BluetoothAudioProvider::startSession(hostIf, audioConfig, _hidl_cb);
+}
+
+Return<void> A2dpOffloadAudioProvider::onSessionReady(
+    startSession_cb _hidl_cb) {
+  BluetoothAudioSessionReport_2_2::OnSessionStarted(session_type_, stack_iface_,
+                                                    nullptr, audio_config_);
+  _hidl_cb(BluetoothAudioStatus::SUCCESS, DataMQ::Descriptor());
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.h b/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.h
new file mode 100644
index 0000000..7ccdedc
--- /dev/null
+++ b/bluetooth/audio/2.2/default/A2dpOffloadAudioProvider.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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 "BluetoothAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+class A2dpOffloadAudioProvider : public BluetoothAudioProvider {
+ public:
+  A2dpOffloadAudioProvider();
+
+  bool isValid(const V2_1::SessionType& sessionType) override;
+  bool isValid(const V2_0::SessionType& sessionType) override;
+
+  Return<void> startSession(const sp<V2_0::IBluetoothAudioPort>& hostIf,
+                            const V2_0::AudioConfiguration& audioConfig,
+                            startSession_cb _hidl_cb) override;
+
+ private:
+  Return<void> onSessionReady(startSession_cb _hidl_cb) override;
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.cpp b/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.cpp
new file mode 100644
index 0000000..0d918e1
--- /dev/null
+++ b/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderA2dpSoftware"
+
+#include "A2dpSoftwareAudioProvider.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
+
+// Here the buffer size is based on SBC
+static constexpr uint32_t kPcmFrameSize = 4;  // 16 bits per sample / stereo
+// SBC is 128, and here we choose the LCM of 16, 24, and 32
+static constexpr uint32_t kPcmFrameCount = 96;
+static constexpr uint32_t kRtpFrameSize = kPcmFrameSize * kPcmFrameCount;
+// The max counts by 1 tick (20ms) for SBC is about 7. Since using 96 for the
+// PCM counts, here we just choose a greater number
+static constexpr uint32_t kRtpFrameCount = 10;
+static constexpr uint32_t kBufferSize = kRtpFrameSize * kRtpFrameCount;
+static constexpr uint32_t kBufferCount = 2;  // double buffer
+static constexpr uint32_t kDataMqSize = kBufferSize * kBufferCount;
+
+A2dpSoftwareAudioProvider::A2dpSoftwareAudioProvider()
+    : BluetoothAudioProvider(), mDataMQ(nullptr) {
+  LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize
+            << " byte(s)";
+  std::unique_ptr<DataMQ> tempDataMQ(
+      new DataMQ(kDataMqSize, /* EventFlag */ true));
+  if (tempDataMQ && tempDataMQ->isValid()) {
+    mDataMQ = std::move(tempDataMQ);
+    session_type_ = V2_1::SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH;
+  } else {
+    ALOGE_IF(!tempDataMQ, "failed to allocate data MQ");
+    ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "data MQ is invalid");
+  }
+}
+
+bool A2dpSoftwareAudioProvider::isValid(const V2_0::SessionType& sessionType) {
+  return isValid(static_cast<V2_1::SessionType>(sessionType));
+}
+
+bool A2dpSoftwareAudioProvider::isValid(const V2_1::SessionType& sessionType) {
+  return (sessionType == session_type_ && mDataMQ && mDataMQ->isValid());
+}
+
+Return<void> A2dpSoftwareAudioProvider::startSession(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  if (audioConfig.getDiscriminator() !=
+      AudioConfiguration::hidl_discriminator::pcmConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  } else if (!android::bluetooth::audio::IsSoftwarePcmConfigurationValid(
+                 audioConfig.pcmConfig())) {
+    LOG(WARNING) << __func__ << " - Unsupported PCM Configuration="
+                 << toString(audioConfig.pcmConfig());
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  return BluetoothAudioProvider::startSession(hostIf, audioConfig, _hidl_cb);
+}
+
+Return<void> A2dpSoftwareAudioProvider::onSessionReady(
+    startSession_cb _hidl_cb) {
+  if (mDataMQ && mDataMQ->isValid()) {
+    BluetoothAudioSessionReport_2_2::OnSessionStarted(
+        session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
+    _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
+  } else {
+    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
+  }
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.h b/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.h
new file mode 100644
index 0000000..3d4f0cc
--- /dev/null
+++ b/bluetooth/audio/2.2/default/A2dpSoftwareAudioProvider.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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 <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include "BluetoothAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+class A2dpSoftwareAudioProvider : public BluetoothAudioProvider {
+ public:
+  A2dpSoftwareAudioProvider();
+
+  bool isValid(const V2_1::SessionType& sessionType) override;
+  bool isValid(const V2_0::SessionType& sessionType) override;
+
+  Return<void> startSession(const sp<IBluetoothAudioPort>& hostIf,
+                            const V2_0::AudioConfiguration& audioConfig,
+                            startSession_cb _hidl_cb) override;
+
+ private:
+  // audio data queue for software encoding
+  std::unique_ptr<DataMQ> mDataMQ;
+
+  Return<void> onSessionReady(startSession_cb _hidl_cb) override;
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/Android.bp b/bluetooth/audio/2.2/default/Android.bp
new file mode 100644
index 0000000..7a5ae75
--- /dev/null
+++ b/bluetooth/audio/2.2/default/Android.bp
@@ -0,0 +1,37 @@
+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_shared {
+    name: "android.hardware.bluetooth.audio@2.2-impl",
+    defaults: ["hidl_defaults"],
+    vendor: true,
+    relative_install_path: "hw",
+    srcs: [
+        "BluetoothAudioProvidersFactory.cpp",
+        "BluetoothAudioProvider.cpp",
+        "A2dpOffloadAudioProvider.cpp",
+        "A2dpSoftwareAudioProvider.cpp",
+        "HearingAidAudioProvider.cpp",
+        "LeAudioAudioProvider.cpp",
+        "LeAudioOffloadAudioProvider.cpp",
+    ],
+    header_libs: ["libhardware_headers"],
+    shared_libs: [
+        "android.hardware.bluetooth.audio@2.0",
+        "android.hardware.bluetooth.audio@2.1",
+        "android.hardware.bluetooth.audio@2.2",
+        "libbase",
+        "libbluetooth_audio_session",
+        "libcutils",
+        "libfmq",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+    ],
+}
diff --git a/bluetooth/audio/2.2/default/BluetoothAudioProvider.cpp b/bluetooth/audio/2.2/default/BluetoothAudioProvider.cpp
new file mode 100644
index 0000000..3655bc0
--- /dev/null
+++ b/bluetooth/audio/2.2/default/BluetoothAudioProvider.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderStub"
+
+#include "BluetoothAudioProvider.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::Void;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+void BluetoothAudioDeathRecipient::serviceDied(
+    uint64_t cookie __unused,
+    const wp<::android::hidl::base::V1_0::IBase>& who __unused) {
+  LOG(ERROR) << "BluetoothAudioDeathRecipient::" << __func__
+             << " - BluetoothAudio Service died";
+  provider_->endSession();
+}
+
+BluetoothAudioProvider::BluetoothAudioProvider()
+    : death_recipient_(new BluetoothAudioDeathRecipient(this)),
+      session_type_(V2_1::SessionType::UNKNOWN),
+      audio_config_({}) {}
+
+Return<void> BluetoothAudioProvider::startSession(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const V2_0::AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  AudioConfiguration audioConfig_2_2;
+
+  if (audioConfig.getDiscriminator() ==
+      V2_0::AudioConfiguration::hidl_discriminator::pcmConfig) {
+    audioConfig_2_2.pcmConfig(
+        {.sampleRate =
+             static_cast<V2_1::SampleRate>(audioConfig.pcmConfig().sampleRate),
+         .channelMode = audioConfig.pcmConfig().channelMode,
+         .bitsPerSample = audioConfig.pcmConfig().bitsPerSample,
+         .dataIntervalUs = 0});
+  } else {
+    audioConfig_2_2.codecConfig(audioConfig.codecConfig());
+  }
+
+  return startSession_2_2(hostIf, audioConfig_2_2, _hidl_cb);
+}
+
+Return<void> BluetoothAudioProvider::startSession_2_1(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const V2_1::AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  AudioConfiguration audioConfig_2_2;
+  if (audioConfig.getDiscriminator() ==
+      V2_1::AudioConfiguration::hidl_discriminator::leAudioCodecConfig) {
+    audioConfig_2_2.leAudioConfig().mode = LeAudioMode::UNKNOWN;
+    audioConfig_2_2.leAudioConfig().config.unicastConfig() = {
+        .streamMap = {{
+            .streamHandle = 0xFFFF,
+            .audioChannelAllocation =
+                audioConfig.leAudioCodecConfig().audioChannelAllocation,
+        }},
+        .peerDelay = 0,
+        .lc3Config = audioConfig.leAudioCodecConfig().lc3Config};
+  } else if (audioConfig.getDiscriminator() ==
+             V2_1::AudioConfiguration::hidl_discriminator::pcmConfig) {
+    audioConfig_2_2.pcmConfig(audioConfig.pcmConfig());
+  } else {
+    audioConfig_2_2.codecConfig(audioConfig.codecConfig());
+  }
+
+  return startSession_2_2(hostIf, audioConfig_2_2, _hidl_cb);
+}
+
+Return<void> BluetoothAudioProvider::startSession_2_2(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  if (hostIf == nullptr) {
+    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
+    return Void();
+  }
+
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  audio_config_ = audioConfig;
+  stack_iface_ = hostIf;
+  stack_iface_->linkToDeath(death_recipient_, 0);
+
+  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
+            << ", AudioConfiguration=[" << toString(audio_config_) << "]";
+
+  onSessionReady(_hidl_cb);
+  return Void();
+}
+
+Return<void> BluetoothAudioProvider::streamStarted(
+    BluetoothAudioStatus status) {
+  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
+            << ", status=" << toString(status);
+
+  /**
+   * Streaming on control path has started,
+   * HAL server should start the streaming on data path.
+   */
+  if (stack_iface_) {
+    BluetoothAudioSessionReport_2_2::ReportControlStatus(session_type_, true,
+                                                         status);
+  } else {
+    LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
+                 << ", status=" << toString(status) << " has NO session";
+  }
+
+  return Void();
+}
+
+Return<void> BluetoothAudioProvider::streamSuspended(
+    BluetoothAudioStatus status) {
+  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
+            << ", status=" << toString(status);
+
+  /**
+   * Streaming on control path has suspend,
+   * HAL server should suspend the streaming on data path.
+   */
+  if (stack_iface_) {
+    BluetoothAudioSessionReport_2_2::ReportControlStatus(session_type_, false,
+                                                         status);
+  } else {
+    LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
+                 << ", status=" << toString(status) << " has NO session";
+  }
+
+  return Void();
+}
+
+Return<void> BluetoothAudioProvider::endSession() {
+  LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
+
+  if (stack_iface_) {
+    BluetoothAudioSessionReport_2_2::OnSessionEnded(session_type_);
+    stack_iface_->unlinkToDeath(death_recipient_);
+  } else {
+    LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
+              << " has NO session";
+  }
+
+  /**
+   * Clean up the audio platform as remote audio device is no
+   * longer active
+   */
+  stack_iface_ = nullptr;
+  audio_config_ = {};
+
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/BluetoothAudioProvider.h b/bluetooth/audio/2.2/default/BluetoothAudioProvider.h
new file mode 100644
index 0000000..b7581ba
--- /dev/null
+++ b/bluetooth/audio/2.2/default/BluetoothAudioProvider.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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/hardware/bluetooth/audio/2.2/IBluetoothAudioProvider.h>
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::bluetooth::audio::V2_0::IBluetoothAudioPort;
+
+using BluetoothAudioStatus =
+    ::android::hardware::bluetooth::audio::V2_0::Status;
+
+class BluetoothAudioDeathRecipient;
+
+class BluetoothAudioProvider : public IBluetoothAudioProvider {
+ public:
+  BluetoothAudioProvider();
+  ~BluetoothAudioProvider() = default;
+
+  virtual bool isValid(const V2_1::SessionType& sessionType) = 0;
+  virtual bool isValid(const V2_0::SessionType& sessionType) = 0;
+
+  Return<void> startSession(const sp<IBluetoothAudioPort>& hostIf,
+                            const V2_0::AudioConfiguration& audioConfig,
+                            startSession_cb _hidl_cb) override;
+  Return<void> startSession_2_1(const sp<IBluetoothAudioPort>& hostIf,
+                                const V2_1::AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+  Return<void> startSession_2_2(const sp<IBluetoothAudioPort>& hostIf,
+                                const AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+  Return<void> streamStarted(BluetoothAudioStatus status) override;
+  Return<void> streamSuspended(BluetoothAudioStatus status) override;
+  Return<void> endSession() override;
+
+ protected:
+  sp<BluetoothAudioDeathRecipient> death_recipient_;
+
+  V2_1::SessionType session_type_;
+  AudioConfiguration audio_config_;
+  sp<V2_0::IBluetoothAudioPort> stack_iface_;
+
+  virtual Return<void> onSessionReady(startSession_cb _hidl_cb) = 0;
+};
+
+class BluetoothAudioDeathRecipient : public hidl_death_recipient {
+ public:
+  BluetoothAudioDeathRecipient(const sp<BluetoothAudioProvider> provider)
+      : provider_(provider) {}
+
+  virtual void serviceDied(
+      uint64_t cookie,
+      const wp<::android::hidl::base::V1_0::IBase>& who) override;
+
+ private:
+  sp<BluetoothAudioProvider> provider_;
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.cpp b/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.cpp
new file mode 100644
index 0000000..7438c80
--- /dev/null
+++ b/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProvidersFactory"
+
+#include "BluetoothAudioProvidersFactory.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::CodecCapabilities;
+
+A2dpSoftwareAudioProvider
+    BluetoothAudioProvidersFactory::a2dp_software_provider_instance_;
+A2dpOffloadAudioProvider
+    BluetoothAudioProvidersFactory::a2dp_offload_provider_instance_;
+HearingAidAudioProvider
+    BluetoothAudioProvidersFactory::hearing_aid_provider_instance_;
+LeAudioOutputAudioProvider
+    BluetoothAudioProvidersFactory::leaudio_output_provider_instance_;
+LeAudioOffloadOutputAudioProvider
+    BluetoothAudioProvidersFactory::leaudio_offload_output_provider_instance_;
+LeAudioInputAudioProvider
+    BluetoothAudioProvidersFactory::leaudio_input_provider_instance_;
+LeAudioOffloadInputAudioProvider
+    BluetoothAudioProvidersFactory::leaudio_offload_input_provider_instance_;
+
+Return<void> BluetoothAudioProvidersFactory::openProvider(
+    const V2_0::SessionType sessionType, openProvider_cb _hidl_cb) {
+  LOG(INFO) << __func__ << " - SessionType=" << toString(sessionType);
+  BluetoothAudioStatus status = BluetoothAudioStatus::SUCCESS;
+  BluetoothAudioProvider* provider = nullptr;
+
+  switch (sessionType) {
+    case V2_0::SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH:
+      provider = &a2dp_software_provider_instance_;
+      break;
+    case V2_0::SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH:
+      provider = &a2dp_offload_provider_instance_;
+      break;
+    case V2_0::SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH:
+      provider = &hearing_aid_provider_instance_;
+      break;
+    default:
+      status = BluetoothAudioStatus::FAILURE;
+  }
+
+  if (provider == nullptr || !provider->isValid(sessionType)) {
+    provider = nullptr;
+    status = BluetoothAudioStatus::FAILURE;
+    LOG(ERROR) << __func__ << " - SessionType=" << toString(sessionType)
+               << ", status=" << toString(status);
+  }
+
+  _hidl_cb(status, provider);
+  return Void();
+}
+
+Return<void> BluetoothAudioProvidersFactory::openProvider_2_1(
+    const V2_1::SessionType sessionType, openProvider_2_1_cb _hidl_cb) {
+  LOG(INFO) << __func__ << " - SessionType=" << toString(sessionType);
+  BluetoothAudioStatus status = BluetoothAudioStatus::SUCCESS;
+  BluetoothAudioProvider* provider = nullptr;
+
+  switch (sessionType) {
+    case V2_1::SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH:
+      provider = &a2dp_software_provider_instance_;
+      break;
+    case V2_1::SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH:
+      provider = &a2dp_offload_provider_instance_;
+      break;
+    case V2_1::SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH:
+      provider = &hearing_aid_provider_instance_;
+      break;
+    case V2_1::SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH:
+      provider = &leaudio_output_provider_instance_;
+      break;
+    case V2_1::SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH:
+      provider = &leaudio_offload_output_provider_instance_;
+      break;
+    case V2_1::SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH:
+      provider = &leaudio_input_provider_instance_;
+      break;
+    case V2_1::SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH:
+      provider = &leaudio_offload_input_provider_instance_;
+      break;
+    default:
+      status = BluetoothAudioStatus::FAILURE;
+  }
+
+  if (provider == nullptr || !provider->isValid(sessionType)) {
+    provider = nullptr;
+    status = BluetoothAudioStatus::FAILURE;
+    LOG(ERROR) << __func__ << " - SessionType=" << toString(sessionType)
+               << ", status=" << toString(status);
+  }
+
+  _hidl_cb(status, provider);
+  return Void();
+}
+
+Return<void> BluetoothAudioProvidersFactory::getProviderCapabilities(
+    const V2_0::SessionType sessionType, getProviderCapabilities_cb _hidl_cb) {
+  hidl_vec<V2_0::AudioCapabilities> audio_capabilities =
+      hidl_vec<V2_0::AudioCapabilities>(0);
+  if (sessionType == V2_0::SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
+    std::vector<CodecCapabilities> db_codec_capabilities =
+        android::bluetooth::audio::GetOffloadCodecCapabilities(sessionType);
+    if (db_codec_capabilities.size()) {
+      audio_capabilities.resize(db_codec_capabilities.size());
+      for (int i = 0; i < db_codec_capabilities.size(); ++i) {
+        audio_capabilities[i].codecCapabilities(db_codec_capabilities[i]);
+      }
+    }
+  } else if (sessionType != V2_0::SessionType::UNKNOWN) {
+    std::vector<::android::hardware::bluetooth::audio::V2_0::PcmParameters>
+        db_pcm_capabilities =
+            android::bluetooth::audio::GetSoftwarePcmCapabilities();
+    if (db_pcm_capabilities.size() == 1) {
+      audio_capabilities.resize(1);
+      audio_capabilities[0].pcmCapabilities(db_pcm_capabilities[0]);
+    }
+  }
+  LOG(INFO) << __func__ << " - SessionType=" << toString(sessionType)
+            << " supports " << audio_capabilities.size() << " codecs";
+  _hidl_cb(audio_capabilities);
+  return Void();
+}
+
+Return<void> BluetoothAudioProvidersFactory::getProviderCapabilities_2_1(
+    const V2_1::SessionType sessionType,
+    getProviderCapabilities_2_1_cb _hidl_cb) {
+  hidl_vec<V2_1::AudioCapabilities> audio_capabilities =
+      hidl_vec<V2_1::AudioCapabilities>(0);
+  if (sessionType == V2_1::SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
+    std::vector<CodecCapabilities> db_codec_capabilities =
+        android::bluetooth::audio::GetOffloadCodecCapabilities(sessionType);
+    if (db_codec_capabilities.size()) {
+      audio_capabilities.resize(db_codec_capabilities.size());
+      for (int i = 0; i < db_codec_capabilities.size(); ++i) {
+        audio_capabilities[i].codecCapabilities(db_codec_capabilities[i]);
+      }
+    }
+  } else if (sessionType != V2_1::SessionType::UNKNOWN) {
+    std::vector<V2_1::PcmParameters> db_pcm_capabilities =
+        android::bluetooth::audio::GetSoftwarePcmCapabilities_2_1();
+    if (db_pcm_capabilities.size() == 1) {
+      audio_capabilities.resize(1);
+      audio_capabilities[0].pcmCapabilities(db_pcm_capabilities[0]);
+    }
+  }
+  LOG(INFO) << __func__ << " - SessionType=" << toString(sessionType)
+            << " supports " << audio_capabilities.size() << " codecs";
+  _hidl_cb(audio_capabilities);
+  return Void();
+}
+
+IBluetoothAudioProvidersFactory* HIDL_FETCH_IBluetoothAudioProvidersFactory(
+    const char* /* name */) {
+  return new BluetoothAudioProvidersFactory();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.h b/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.h
new file mode 100644
index 0000000..8db330b
--- /dev/null
+++ b/bluetooth/audio/2.2/default/BluetoothAudioProvidersFactory.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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/hardware/bluetooth/audio/2.2/IBluetoothAudioProvidersFactory.h>
+
+#include "A2dpOffloadAudioProvider.h"
+#include "A2dpSoftwareAudioProvider.h"
+#include "BluetoothAudioProvider.h"
+#include "HearingAidAudioProvider.h"
+#include "LeAudioAudioProvider.h"
+#include "LeAudioOffloadAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+class BluetoothAudioProvidersFactory : public IBluetoothAudioProvidersFactory {
+ public:
+  BluetoothAudioProvidersFactory() {}
+
+  Return<void> openProvider(const V2_0::SessionType sessionType,
+                            openProvider_cb _hidl_cb) override;
+
+  Return<void> getProviderCapabilities(
+      const V2_0::SessionType sessionType,
+      getProviderCapabilities_cb _hidl_cb) override;
+
+  Return<void> openProvider_2_1(const V2_1::SessionType sessionType,
+                                openProvider_2_1_cb _hidl_cb) override;
+
+  Return<void> getProviderCapabilities_2_1(
+      const V2_1::SessionType sessionType,
+      getProviderCapabilities_2_1_cb _hidl_cb) override;
+
+ private:
+  static A2dpSoftwareAudioProvider a2dp_software_provider_instance_;
+  static A2dpOffloadAudioProvider a2dp_offload_provider_instance_;
+  static HearingAidAudioProvider hearing_aid_provider_instance_;
+  static LeAudioOutputAudioProvider leaudio_output_provider_instance_;
+  static LeAudioInputAudioProvider leaudio_input_provider_instance_;
+  static LeAudioOffloadOutputAudioProvider
+      leaudio_offload_output_provider_instance_;
+  static LeAudioOffloadInputAudioProvider
+      leaudio_offload_input_provider_instance_;
+};
+
+extern "C" IBluetoothAudioProvidersFactory*
+HIDL_FETCH_IBluetoothAudioProvidersFactory(const char* name);
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/HearingAidAudioProvider.cpp b/bluetooth/audio/2.2/default/HearingAidAudioProvider.cpp
new file mode 100644
index 0000000..c79b910
--- /dev/null
+++ b/bluetooth/audio/2.2/default/HearingAidAudioProvider.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderHearingAid"
+
+#include "HearingAidAudioProvider.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration;
+
+static constexpr uint32_t kPcmFrameSize = 4;  // 16 bits per sample / stereo
+static constexpr uint32_t kPcmFrameCount = 128;
+static constexpr uint32_t kRtpFrameSize = kPcmFrameSize * kPcmFrameCount;
+static constexpr uint32_t kRtpFrameCount = 7;  // max counts by 1 tick (20ms)
+static constexpr uint32_t kBufferSize = kRtpFrameSize * kRtpFrameCount;
+static constexpr uint32_t kBufferCount = 1;  // single buffer
+static constexpr uint32_t kDataMqSize = kBufferSize * kBufferCount;
+
+HearingAidAudioProvider::HearingAidAudioProvider()
+    : BluetoothAudioProvider(), mDataMQ(nullptr) {
+  LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize
+            << " byte(s)";
+  std::unique_ptr<DataMQ> tempDataMQ(
+      new DataMQ(kDataMqSize, /* EventFlag */ true));
+  if (tempDataMQ && tempDataMQ->isValid()) {
+    mDataMQ = std::move(tempDataMQ);
+    session_type_ = V2_1::SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH;
+  } else {
+    ALOGE_IF(!tempDataMQ, "failed to allocate data MQ");
+    ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "data MQ is invalid");
+  }
+}
+
+bool HearingAidAudioProvider::isValid(const V2_0::SessionType& sessionType) {
+  return isValid(static_cast<V2_1::SessionType>(sessionType));
+}
+
+bool HearingAidAudioProvider::isValid(const V2_1::SessionType& sessionType) {
+  return (sessionType == session_type_ && mDataMQ && mDataMQ->isValid());
+}
+
+Return<void> HearingAidAudioProvider::startSession(
+    const sp<IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  if (audioConfig.getDiscriminator() !=
+      AudioConfiguration::hidl_discriminator::pcmConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  } else if (!android::bluetooth::audio::IsSoftwarePcmConfigurationValid(
+                 audioConfig.pcmConfig())) {
+    LOG(WARNING) << __func__ << " - Unsupported PCM Configuration="
+                 << toString(audioConfig.pcmConfig());
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  return BluetoothAudioProvider::startSession(hostIf, audioConfig, _hidl_cb);
+}
+
+Return<void> HearingAidAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
+  if (mDataMQ && mDataMQ->isValid()) {
+    BluetoothAudioSessionReport_2_2::OnSessionStarted(
+        session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
+    _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
+  } else {
+    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
+  }
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/HearingAidAudioProvider.h b/bluetooth/audio/2.2/default/HearingAidAudioProvider.h
new file mode 100644
index 0000000..426c443
--- /dev/null
+++ b/bluetooth/audio/2.2/default/HearingAidAudioProvider.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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 <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include "BluetoothAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+class HearingAidAudioProvider : public BluetoothAudioProvider {
+ public:
+  HearingAidAudioProvider();
+
+  bool isValid(const V2_1::SessionType& sessionType) override;
+  bool isValid(const V2_0::SessionType& sessionType) override;
+
+  Return<void> startSession(const sp<IBluetoothAudioPort>& hostIf,
+                            const V2_0::AudioConfiguration& audioConfig,
+                            startSession_cb _hidl_cb) override;
+
+ private:
+  // audio data queue for software encoding
+  std::unique_ptr<DataMQ> mDataMQ;
+
+  Return<void> onSessionReady(startSession_cb _hidl_cb) override;
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/LeAudioAudioProvider.cpp b/bluetooth/audio/2.2/default/LeAudioAudioProvider.cpp
new file mode 100644
index 0000000..af6ec99
--- /dev/null
+++ b/bluetooth/audio/2.2/default/LeAudioAudioProvider.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderLeAudio"
+
+#include "LeAudioAudioProvider.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
+using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
+using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
+
+static constexpr uint32_t kBufferOutCount = 2;  // two frame buffer
+static constexpr uint32_t kBufferInCount = 2;   // two frame buffer
+
+LeAudioOutputAudioProvider::LeAudioOutputAudioProvider()
+    : LeAudioAudioProvider() {
+  session_type_ = V2_1::SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
+}
+
+LeAudioInputAudioProvider::LeAudioInputAudioProvider()
+    : LeAudioAudioProvider() {
+  session_type_ = V2_1::SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH;
+}
+
+LeAudioAudioProvider::LeAudioAudioProvider()
+    : BluetoothAudioProvider(), mDataMQ(nullptr) {}
+
+bool LeAudioAudioProvider::isValid(const V2_0::SessionType& sessionType) {
+  LOG(ERROR) << __func__ << ", invalid session type for Le Audio provider: "
+             << toString(sessionType);
+
+  return false;
+}
+
+bool LeAudioAudioProvider::isValid(const V2_1::SessionType& sessionType) {
+  return (sessionType == session_type_);
+}
+
+Return<void> LeAudioAudioProvider::startSession_2_1(
+    const sp<V2_0::IBluetoothAudioPort>& hostIf,
+    const V2_1::AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  if (audioConfig.getDiscriminator() !=
+      V2_1::AudioConfiguration::hidl_discriminator::pcmConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  AudioConfiguration audioConfig_2_2;
+  audioConfig_2_2.pcmConfig(
+      {.sampleRate =
+           static_cast<V2_1::SampleRate>(audioConfig.pcmConfig().sampleRate),
+       .channelMode = audioConfig.pcmConfig().channelMode,
+       .bitsPerSample = audioConfig.pcmConfig().bitsPerSample,
+       .dataIntervalUs = 0});
+
+  return startSession_2_2(hostIf, audioConfig_2_2, _hidl_cb);
+}
+
+Return<void> LeAudioAudioProvider::startSession_2_2(
+    const sp<V2_0::IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  if (audioConfig.getDiscriminator() !=
+      AudioConfiguration::hidl_discriminator::pcmConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  } else if (!android::bluetooth::audio::IsSoftwarePcmConfigurationValid_2_1(
+                 audioConfig.pcmConfig())) {
+    LOG(WARNING) << __func__ << " - Unsupported PCM Configuration="
+                 << toString(audioConfig.pcmConfig());
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  uint32_t kDataMqSize = 0;
+  switch (audioConfig.pcmConfig().sampleRate) {
+    case SampleRate::RATE_8000:
+      kDataMqSize = 8000;
+      break;
+    case SampleRate::RATE_16000:
+      kDataMqSize = 16000;
+      break;
+    case SampleRate::RATE_24000:
+      kDataMqSize = 24000;
+      break;
+    case SampleRate::RATE_32000:
+      kDataMqSize = 32000;
+      break;
+    case SampleRate::RATE_44100:
+      kDataMqSize = 44100;
+      break;
+    case SampleRate::RATE_48000:
+      kDataMqSize = 48000;
+      break;
+    default:
+      LOG(WARNING) << __func__ << " - Unsupported sampling frequency="
+                   << toString(audioConfig.pcmConfig());
+      _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+               DataMQ::Descriptor());
+      return Void();
+  }
+
+  /* Number of samples per millisecond */
+  kDataMqSize = ceil(kDataMqSize / 1000);
+
+  switch (audioConfig.pcmConfig().channelMode) {
+    case ChannelMode::MONO:
+      break;
+    case ChannelMode::STEREO:
+      kDataMqSize *= 2;
+      break;
+    default:
+      /* This should never happen it would be caught while validating
+       * parameters.
+       */
+      break;
+  }
+
+  switch (audioConfig.pcmConfig().bitsPerSample) {
+    case BitsPerSample::BITS_16:
+      kDataMqSize *= 2;
+      break;
+    case BitsPerSample::BITS_24:
+      kDataMqSize *= 3;
+      break;
+    case BitsPerSample::BITS_32:
+      kDataMqSize *= 4;
+      break;
+    default:
+      /* This should never happen it would be caught while validating
+       * parameters.
+       */
+      break;
+  }
+
+  if (session_type_ == V2_1::SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH)
+    kDataMqSize *= kBufferOutCount;
+  else if (session_type_ ==
+           V2_1::SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH)
+    kDataMqSize *= kBufferInCount;
+  else
+    LOG(WARNING) << __func__ << ", default single buffer used";
+
+  kDataMqSize *= audioConfig.pcmConfig().dataIntervalUs / 1000;
+
+  LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize
+            << " byte(s)";
+
+  std::unique_ptr<DataMQ> tempDataMQ(
+      new DataMQ(kDataMqSize, /* EventFlag */ true));
+  if (tempDataMQ && tempDataMQ->isValid()) {
+    mDataMQ = std::move(tempDataMQ);
+  } else {
+    ALOGE_IF(!tempDataMQ, "failed to allocate data MQ");
+    ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "data MQ is invalid");
+    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
+    return Void();
+  }
+
+  return BluetoothAudioProvider::startSession_2_2(hostIf, audioConfig,
+                                                  _hidl_cb);
+}
+
+Return<void> LeAudioAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
+  if (mDataMQ && mDataMQ->isValid()) {
+    BluetoothAudioSessionReport_2_2::OnSessionStarted(
+        session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
+    _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
+  } else {
+    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
+  }
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/LeAudioAudioProvider.h b/bluetooth/audio/2.2/default/LeAudioAudioProvider.h
new file mode 100644
index 0000000..40c26e0
--- /dev/null
+++ b/bluetooth/audio/2.2/default/LeAudioAudioProvider.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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/hardware/bluetooth/audio/2.2/types.h>
+#include <fmq/MessageQueue.h>
+#include <hidl/MQDescriptor.h>
+
+#include "BluetoothAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::hardware::kSynchronizedReadWrite;
+using ::android::hardware::MessageQueue;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+class LeAudioAudioProvider : public BluetoothAudioProvider {
+ public:
+  LeAudioAudioProvider();
+
+  bool isValid(const V2_1::SessionType& sessionType) override;
+  bool isValid(const V2_0::SessionType& sessionType) override;
+
+  Return<void> startSession_2_1(const sp<V2_0::IBluetoothAudioPort>& hostIf,
+                                const V2_1::AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+
+  Return<void> startSession_2_2(const sp<V2_0::IBluetoothAudioPort>& hostIf,
+                                const AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+
+ private:
+  /** queue for software encodec/decoded audio data */
+  std::unique_ptr<DataMQ> mDataMQ;
+
+  Return<void> onSessionReady(startSession_cb _hidl_cb) override;
+};
+
+class LeAudioOutputAudioProvider : public LeAudioAudioProvider {
+ public:
+  LeAudioOutputAudioProvider();
+};
+
+class LeAudioInputAudioProvider : public LeAudioAudioProvider {
+ public:
+  LeAudioInputAudioProvider();
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.cpp b/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.cpp
new file mode 100644
index 0000000..7b70654
--- /dev/null
+++ b/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderLeAudioOffload"
+
+#include "LeAudioOffloadAudioProvider.h"
+
+#include <android-base/logging.h>
+
+#include "BluetoothAudioSessionReport_2_2.h"
+#include "BluetoothAudioSupportedCodecsDB_2_1.h"
+#include "BluetoothAudioSupportedCodecsDB_2_2.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_2;
+using ::android::hardware::Void;
+using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
+using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
+using ::android::hardware::bluetooth::audio::V2_1::SampleRate;
+
+using DataMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+
+LeAudioOffloadOutputAudioProvider::LeAudioOffloadOutputAudioProvider()
+    : LeAudioOffloadAudioProvider() {
+  session_type_ =
+      V2_1::SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH;
+}
+
+LeAudioOffloadInputAudioProvider::LeAudioOffloadInputAudioProvider()
+    : LeAudioOffloadAudioProvider() {
+  session_type_ =
+      V2_1::SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH;
+}
+
+LeAudioOffloadAudioProvider::LeAudioOffloadAudioProvider()
+    : BluetoothAudioProvider() {}
+
+bool LeAudioOffloadAudioProvider::isValid(
+    const V2_0::SessionType& sessionType) {
+  LOG(ERROR) << __func__
+             << ", invalid session type for Offloaded Le Audio provider: "
+             << toString(sessionType);
+
+  return false;
+}
+
+bool LeAudioOffloadAudioProvider::isValid(
+    const V2_1::SessionType& sessionType) {
+  return (sessionType == session_type_);
+}
+
+Return<void> LeAudioOffloadAudioProvider::startSession_2_1(
+    const sp<V2_0::IBluetoothAudioPort>& hostIf,
+    const V2_1::AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  if (audioConfig.getDiscriminator() !=
+      V2_1::AudioConfiguration::hidl_discriminator::leAudioCodecConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  AudioConfiguration audioConfig_2_2;
+  audioConfig_2_2.leAudioConfig().mode = LeAudioMode::UNKNOWN;
+  audioConfig_2_2.leAudioConfig().config.unicastConfig() = {
+      .streamMap = {{
+          .streamHandle = 0xFFFF,
+          .audioChannelAllocation =
+              audioConfig.leAudioCodecConfig().audioChannelAllocation,
+      }},
+      .peerDelay = 0,
+      .lc3Config = audioConfig.leAudioCodecConfig().lc3Config};
+
+  return startSession_2_2(hostIf, audioConfig_2_2, _hidl_cb);
+}
+
+Return<void> LeAudioOffloadAudioProvider::startSession_2_2(
+    const sp<V2_0::IBluetoothAudioPort>& hostIf,
+    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
+  /**
+   * Initialize the audio platform if audioConfiguration is supported.
+   * Save the IBluetoothAudioPort interface, so that it can be used
+   * later to send stream control commands to the HAL client, based on
+   * interaction with Audio framework.
+   */
+  if (audioConfig.getDiscriminator() !=
+      AudioConfiguration::hidl_discriminator::leAudioConfig) {
+    LOG(WARNING) << __func__
+                 << " - Invalid Audio Configuration=" << toString(audioConfig);
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  if (!android::bluetooth::audio::IsOffloadLeAudioConfigurationValid(
+          session_type_, audioConfig.leAudioConfig())) {
+    LOG(WARNING) << __func__ << " - Unsupported LC3 Offloaded Configuration="
+                 << toString(audioConfig.leAudioConfig());
+    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
+             DataMQ::Descriptor());
+    return Void();
+  }
+
+  return BluetoothAudioProvider::startSession_2_2(hostIf, audioConfig,
+                                                  _hidl_cb);
+}
+
+Return<void> LeAudioOffloadAudioProvider::onSessionReady(
+    startSession_cb _hidl_cb) {
+  BluetoothAudioSessionReport_2_2::OnSessionStarted(session_type_, stack_iface_,
+                                                    nullptr, audio_config_);
+  _hidl_cb(BluetoothAudioStatus::SUCCESS, DataMQ::Descriptor());
+  return Void();
+}
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.h b/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.h
new file mode 100644
index 0000000..5620295
--- /dev/null
+++ b/bluetooth/audio/2.2/default/LeAudioOffloadAudioProvider.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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/hardware/bluetooth/audio/2.2/types.h>
+
+#include "BluetoothAudioProvider.h"
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+namespace V2_2 {
+namespace implementation {
+
+class LeAudioOffloadAudioProvider : public BluetoothAudioProvider {
+ public:
+  LeAudioOffloadAudioProvider();
+
+  bool isValid(const V2_1::SessionType& sessionType) override;
+  bool isValid(const V2_0::SessionType& sessionType) override;
+
+  Return<void> startSession_2_1(const sp<V2_0::IBluetoothAudioPort>& hostIf,
+                                const V2_1::AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+
+  Return<void> startSession_2_2(const sp<V2_0::IBluetoothAudioPort>& hostIf,
+                                const AudioConfiguration& audioConfig,
+                                startSession_cb _hidl_cb) override;
+
+ private:
+  Return<void> onSessionReady(startSession_cb _hidl_cb) override;
+};
+
+class LeAudioOffloadOutputAudioProvider : public LeAudioOffloadAudioProvider {
+ public:
+  LeAudioOffloadOutputAudioProvider();
+};
+
+class LeAudioOffloadInputAudioProvider : public LeAudioOffloadAudioProvider {
+ public:
+  LeAudioOffloadInputAudioProvider();
+};
+
+}  // namespace implementation
+}  // namespace V2_2
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace hardware
+}  // namespace android
diff --git a/bluetooth/audio/2.2/types.hal b/bluetooth/audio/2.2/types.hal
new file mode 100644
index 0000000..d5f8a3f
--- /dev/null
+++ b/bluetooth/audio/2.2/types.hal
@@ -0,0 +1,72 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.bluetooth.audio@2.2;
+
+import @2.1::Lc3Parameters;
+import @2.1::PcmParameters;
+import @2.0::CodecConfiguration;
+
+enum LeAudioMode : uint8_t {
+    UNKNOWN = 0x00,
+    UNICAST = 0x01,
+    BROADCAST = 0x02,
+};
+
+struct UnicastStreamMap {
+    /* The connection handle used for a unicast or a broadcast group. */
+    uint16_t streamHandle;
+    /* Audio channel allocation is  a bit field, each enabled bit means that given audio direction,
+     * i.e. "left", or "right" is used. Ordering of audio channels comes from the least significant
+     * bit to the most significant bit. */
+    uint32_t audioChannelAllocation;
+};
+
+struct BroadcastStreamMap {
+    /* The connection handle used for a unicast or a broadcast group. */
+    uint16_t streamHandle;
+    /* Audio channel allocation is  a bit field, each enabled bit means that given audio direction,
+     * i.e. "left", or "right" is used. Ordering of audio channels comes from the least significant
+     * bit to the most significant bit. */
+    uint32_t audioChannelAllocation;
+    Lc3Parameters lc3Config;
+};
+
+struct UnicastConfig {
+    vec<UnicastStreamMap> streamMap;
+    uint32_t peerDelay;
+    Lc3Parameters lc3Config;
+};
+
+struct BroadcastConfig {
+    vec<BroadcastStreamMap> streamMap;
+};
+
+struct LeAudioConfiguration {
+    /* The mode of the LE audio */
+    LeAudioMode mode;
+    safe_union CodecConfig {
+        UnicastConfig unicastConfig;
+        BroadcastConfig broadcastConfig;
+    } config;
+};
+
+/** Used to configure either a Hardware or Software Encoding session based on session type */
+safe_union AudioConfiguration {
+    PcmParameters pcmConfig;
+    CodecConfiguration codecConfig;
+    LeAudioConfiguration leAudioConfig;
+};
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
index 551bc50..19d2d92 100644
--- a/bluetooth/audio/utils/Android.bp
+++ b/bluetooth/audio/utils/Android.bp
@@ -14,14 +14,17 @@
     srcs: [
         "session/BluetoothAudioSession.cpp",
         "session/BluetoothAudioSession_2_1.cpp",
+        "session/BluetoothAudioSession_2_2.cpp",
         "session/BluetoothAudioSupportedCodecsDB.cpp",
         "session/BluetoothAudioSupportedCodecsDB_2_1.cpp",
+        "session/BluetoothAudioSupportedCodecsDB_2_2.cpp",
     ],
     export_include_dirs: ["session/"],
     header_libs: ["libhardware_headers"],
     shared_libs: [
         "android.hardware.bluetooth.audio@2.0",
         "android.hardware.bluetooth.audio@2.1",
+        "android.hardware.bluetooth.audio@2.2",
         "libbase",
         "libcutils",
         "libfmq",
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession.h b/bluetooth/audio/utils/session/BluetoothAudioSession.h
index 83e20ad..3469cc0 100644
--- a/bluetooth/audio/utils/session/BluetoothAudioSession.h
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession.h
@@ -80,6 +80,7 @@
 
 class BluetoothAudioSession {
   friend class BluetoothAudioSession_2_1;
+  friend class BluetoothAudioSession_2_2;
 
  private:
   // using recursive_mutex to allow hwbinder to re-enter agian.
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_2.h b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_2.h
new file mode 100644
index 0000000..194259a
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSessionReport_2_2.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 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 "BluetoothAudioSession_2_2.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSessionReport_2_2 {
+ public:
+  // The API reports the Bluetooth stack has started the session, and will
+  // inform registered bluetooth_audio outputs
+  static void OnSessionStarted(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type,
+      const sp<IBluetoothAudioPort> host_iface,
+      const DataMQ::Descriptor* dataMQ,
+      const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration&
+          audio_config) {
+    std::shared_ptr<BluetoothAudioSession_2_2> session_ptr =
+        BluetoothAudioSessionInstance_2_2::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->OnSessionStarted(host_iface, dataMQ, audio_config);
+    }
+  }
+
+  // The API reports the Bluetooth stack has ended the session, and will
+  // inform registered bluetooth_audio outputs
+  static void OnSessionEnded(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type) {
+    std::shared_ptr<BluetoothAudioSession_2_2> session_ptr =
+        BluetoothAudioSessionInstance_2_2::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->GetAudioSession()->OnSessionEnded();
+    }
+  }
+  // The API reports the Bluetooth stack has replied the result of startStream
+  // or suspendStream, and will inform registered bluetooth_audio outputs
+  static void ReportControlStatus(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type,
+      const bool& start_resp, const BluetoothAudioStatus& status) {
+    std::shared_ptr<BluetoothAudioSession_2_2> session_ptr =
+        BluetoothAudioSessionInstance_2_2::GetSessionInstance(session_type);
+    if (session_ptr != nullptr) {
+      session_ptr->GetAudioSession()->ReportControlStatus(start_resp, status);
+    }
+  }
+};
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.cpp b/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.cpp
new file mode 100644
index 0000000..9d9ea41
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2018 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 "BTAudioProviderSession_2_2"
+
+#include "BluetoothAudioSession_2_2.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+using SessionType_2_1 =
+    ::android::hardware::bluetooth::audio::V2_1::SessionType;
+using SessionType_2_0 =
+    ::android::hardware::bluetooth::audio::V2_0::SessionType;
+
+::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+    BluetoothAudioSession_2_2::invalidSoftwareAudioConfiguration = {};
+::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+    BluetoothAudioSession_2_2::invalidOffloadAudioConfiguration = {};
+
+namespace {
+bool is_2_0_session_type(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type) {
+  if (session_type == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH ||
+      session_type == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH ||
+      session_type == SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH) {
+    return true;
+  } else {
+    return false;
+  }
+}
+}  // namespace
+
+BluetoothAudioSession_2_2::BluetoothAudioSession_2_2(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type)
+    : audio_session(BluetoothAudioSessionInstance::GetSessionInstance(
+          static_cast<SessionType_2_0>(session_type))) {
+  if (is_2_0_session_type(session_type)) {
+    session_type_2_1_ = (SessionType_2_1::UNKNOWN);
+  } else {
+    session_type_2_1_ = (session_type);
+  }
+}
+
+bool BluetoothAudioSession_2_2::IsSessionReady() {
+  if (session_type_2_1_ !=
+      SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH) {
+    return audio_session->IsSessionReady();
+  }
+
+  std::lock_guard<std::recursive_mutex> guard(audio_session->mutex_);
+  return audio_session->stack_iface_ != nullptr;
+}
+
+std::shared_ptr<BluetoothAudioSession>
+BluetoothAudioSession_2_2::GetAudioSession() {
+  return audio_session;
+}
+
+// The control function is for the bluetooth_audio module to get the current
+// AudioConfiguration
+const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+BluetoothAudioSession_2_2::GetAudioConfig() {
+  std::lock_guard<std::recursive_mutex> guard(audio_session->mutex_);
+  if (IsSessionReady()) {
+    // If session is unknown it means it should be 2.0 type
+    if (session_type_2_1_ != SessionType_2_1::UNKNOWN) return audio_config_2_2_;
+
+    ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration toConf;
+    const AudioConfiguration fromConf = GetAudioSession()->GetAudioConfig();
+    // pcmConfig only differs between 2.0 and 2.1 in AudioConfiguration
+    if (fromConf.getDiscriminator() ==
+        AudioConfiguration::hidl_discriminator::codecConfig) {
+      toConf.codecConfig() = fromConf.codecConfig();
+    } else {
+      toConf.pcmConfig() = {
+          .sampleRate = static_cast<
+              ::android::hardware::bluetooth::audio::V2_1::SampleRate>(
+              fromConf.pcmConfig().sampleRate),
+          .channelMode = fromConf.pcmConfig().channelMode,
+          .bitsPerSample = fromConf.pcmConfig().bitsPerSample,
+          .dataIntervalUs = 0};
+    }
+    return toConf;
+  } else if (session_type_2_1_ ==
+             SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH) {
+    return kInvalidOffloadAudioConfiguration;
+  } else {
+    return kInvalidSoftwareAudioConfiguration;
+  }
+}
+
+bool BluetoothAudioSession_2_2::UpdateAudioConfig(
+    const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration&
+        audio_config) {
+  bool is_software_session =
+      (session_type_2_1_ == SessionType_2_1::A2DP_SOFTWARE_ENCODING_DATAPATH ||
+       session_type_2_1_ ==
+           SessionType_2_1::HEARING_AID_SOFTWARE_ENCODING_DATAPATH ||
+       session_type_2_1_ ==
+           SessionType_2_1::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH ||
+       session_type_2_1_ ==
+           SessionType_2_1::LE_AUDIO_SOFTWARE_DECODED_DATAPATH);
+  bool is_offload_a2dp_session =
+      (session_type_2_1_ == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH);
+  bool is_offload_le_audio_session =
+      (session_type_2_1_ ==
+           SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH ||
+       session_type_2_1_ ==
+           SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH);
+  auto audio_config_discriminator = audio_config.getDiscriminator();
+  bool is_software_audio_config =
+      (is_software_session &&
+       audio_config_discriminator ==
+           ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration::
+               hidl_discriminator::pcmConfig);
+  bool is_a2dp_offload_audio_config =
+      (is_offload_a2dp_session &&
+       audio_config_discriminator ==
+           ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration::
+               hidl_discriminator::codecConfig);
+  bool is_le_audio_offload_audio_config =
+      (is_offload_le_audio_session &&
+       audio_config_discriminator ==
+           ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration::
+               hidl_discriminator::leAudioConfig);
+  if (!is_software_audio_config && !is_a2dp_offload_audio_config &&
+      !is_le_audio_offload_audio_config) {
+    return false;
+  }
+  audio_config_2_2_ = audio_config;
+  return true;
+}
+
+// The report function is used to report that the Bluetooth stack has started
+// this session without any failure, and will invoke session_changed_cb_ to
+// notify those registered bluetooth_audio outputs
+void BluetoothAudioSession_2_2::OnSessionStarted(
+    const sp<IBluetoothAudioPort> stack_iface, const DataMQ::Descriptor* dataMQ,
+    const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration&
+        audio_config) {
+  if (session_type_2_1_ == SessionType_2_1::UNKNOWN) {
+    ::android::hardware::bluetooth::audio::V2_0::AudioConfiguration config;
+    if (audio_config.getDiscriminator() ==
+        ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration::
+            hidl_discriminator::codecConfig) {
+      config.codecConfig(audio_config.codecConfig());
+    } else {
+      auto& tmpPcm = audio_config.pcmConfig();
+      config.pcmConfig(
+          ::android::hardware::bluetooth::audio::V2_0::PcmParameters{
+              .sampleRate = static_cast<SampleRate>(tmpPcm.sampleRate),
+              .channelMode = tmpPcm.channelMode,
+              .bitsPerSample = tmpPcm.bitsPerSample
+              /*dataIntervalUs is not passed to 2.0 */
+          });
+    }
+
+    audio_session->OnSessionStarted(stack_iface, dataMQ, config);
+  } else {
+    std::lock_guard<std::recursive_mutex> guard(audio_session->mutex_);
+    if (stack_iface == nullptr) {
+      LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_)
+                 << ", IBluetoothAudioPort Invalid";
+    } else if (!UpdateAudioConfig(audio_config)) {
+      LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_)
+                 << ", AudioConfiguration=" << toString(audio_config)
+                 << " Invalid";
+    } else if (!audio_session->UpdateDataPath(dataMQ)) {
+      LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_2_1_)
+                 << " DataMQ Invalid";
+      audio_config_2_2_ =
+          (session_type_2_1_ == SessionType_2_1::A2DP_HARDWARE_OFFLOAD_DATAPATH
+               ? kInvalidOffloadAudioConfiguration
+               : kInvalidSoftwareAudioConfiguration);
+    } else {
+      audio_session->stack_iface_ = stack_iface;
+      LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_2_1_)
+                << ", AudioConfiguration=" << toString(audio_config);
+      audio_session->ReportSessionStatus();
+    };
+  }
+}
+
+std::unique_ptr<BluetoothAudioSessionInstance_2_2>
+    BluetoothAudioSessionInstance_2_2::instance_ptr =
+        std::unique_ptr<BluetoothAudioSessionInstance_2_2>(
+            new BluetoothAudioSessionInstance_2_2());
+
+// API to fetch the session of A2DP / Hearing Aid
+std::shared_ptr<BluetoothAudioSession_2_2>
+BluetoothAudioSessionInstance_2_2::GetSessionInstance(
+    const SessionType_2_1& session_type) {
+  std::lock_guard<std::mutex> guard(instance_ptr->mutex_);
+  if (!instance_ptr->sessions_map_.empty()) {
+    auto entry = instance_ptr->sessions_map_.find(session_type);
+    if (entry != instance_ptr->sessions_map_.end()) {
+      return entry->second;
+    }
+  }
+  std::shared_ptr<BluetoothAudioSession_2_2> session_ptr =
+      std::make_shared<BluetoothAudioSession_2_2>(session_type);
+  instance_ptr->sessions_map_[session_type] = session_ptr;
+  return session_ptr;
+}
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.h b/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.h
new file mode 100644
index 0000000..d3d0bd3
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSession_2_2.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 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/hardware/bluetooth/audio/2.2/types.h>
+
+#include <mutex>
+#include <unordered_map>
+
+#include "BluetoothAudioSession.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothAudioSession_2_2 {
+ private:
+  std::shared_ptr<BluetoothAudioSession> audio_session;
+
+  ::android::hardware::bluetooth::audio::V2_1::SessionType session_type_2_1_;
+
+  // audio data configuration for both software and offloading
+  ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+      audio_config_2_2_;
+
+  bool UpdateAudioConfig(
+      const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration&
+          audio_config);
+
+  static ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+      invalidSoftwareAudioConfiguration;
+  static ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+      invalidOffloadAudioConfiguration;
+
+ public:
+  BluetoothAudioSession_2_2(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type);
+
+  // The function helps to check if this session is ready or not
+  // @return: true if the Bluetooth stack has started the specified session
+  bool IsSessionReady();
+
+  std::shared_ptr<BluetoothAudioSession> GetAudioSession();
+
+  // The report function is used to report that the Bluetooth stack has started
+  // this session without any failure, and will invoke session_changed_cb_ to
+  // notify those registered bluetooth_audio outputs
+  void OnSessionStarted(
+      const sp<IBluetoothAudioPort> stack_iface,
+      const DataMQ::Descriptor* dataMQ,
+      const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration&
+          audio_config);
+
+  // The control function is for the bluetooth_audio module to get the current
+  // AudioConfiguration
+  const ::android::hardware::bluetooth::audio::V2_2::AudioConfiguration
+  GetAudioConfig();
+
+  static constexpr ::android::hardware::bluetooth::audio::V2_2::
+      AudioConfiguration& kInvalidSoftwareAudioConfiguration =
+          invalidSoftwareAudioConfiguration;
+  static constexpr ::android::hardware::bluetooth::audio::V2_2::
+      AudioConfiguration& kInvalidOffloadAudioConfiguration =
+          invalidOffloadAudioConfiguration;
+};
+
+class BluetoothAudioSessionInstance_2_2 {
+ public:
+  // The API is to fetch the specified session of A2DP / Hearing Aid
+  static std::shared_ptr<BluetoothAudioSession_2_2> GetSessionInstance(
+      const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+          session_type);
+
+ private:
+  static std::unique_ptr<BluetoothAudioSessionInstance_2_2> instance_ptr;
+  std::mutex mutex_;
+  std::unordered_map<::android::hardware::bluetooth::audio::V2_1::SessionType,
+                     std::shared_ptr<BluetoothAudioSession_2_2>>
+      sessions_map_;
+};
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.cpp b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.cpp
new file mode 100644
index 0000000..5becdaa
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "BTAudioProviderSessionCodecsDB_2_2"
+
+#include "BluetoothAudioSupportedCodecsDB_2_2.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+using SessionType_2_1 =
+    ::android::hardware::bluetooth::audio::V2_1::SessionType;
+
+bool IsOffloadLeAudioConfigurationValid(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type,
+    const ::android::hardware::bluetooth::audio::V2_2::LeAudioConfiguration&) {
+  if (session_type !=
+          SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH &&
+      session_type !=
+          SessionType_2_1::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) {
+    return false;
+  }
+
+  // TODO: perform checks on le_audio_codec_config once we know supported
+  // parameters
+
+  return true;
+}
+
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.h b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.h
new file mode 100644
index 0000000..59d22b7
--- /dev/null
+++ b/bluetooth/audio/utils/session/BluetoothAudioSupportedCodecsDB_2_2.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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/hardware/bluetooth/audio/2.2/types.h>
+
+#include "BluetoothAudioSupportedCodecsDB.h"
+
+namespace android {
+namespace bluetooth {
+namespace audio {
+
+bool IsOffloadLeAudioConfigurationValid(
+    const ::android::hardware::bluetooth::audio::V2_1::SessionType&
+        session_type,
+    const ::android::hardware::bluetooth::audio::V2_2::LeAudioConfiguration&
+        le_audio_codec_config);
+}  // namespace audio
+}  // namespace bluetooth
+}  // namespace android
diff --git a/camera/metadata/3.7/Android.bp b/camera/metadata/3.7/Android.bp
new file mode 100644
index 0000000..a2908ca
--- /dev/null
+++ b/camera/metadata/3.7/Android.bp
@@ -0,0 +1,17 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.camera.metadata@3.7",
+    root: "android.hardware",
+    srcs: [
+        "types.hal",
+    ],
+    interfaces: [
+        "android.hardware.camera.metadata@3.2",
+        "android.hardware.camera.metadata@3.3",
+        "android.hardware.camera.metadata@3.4",
+        "android.hardware.camera.metadata@3.5",
+        "android.hardware.camera.metadata@3.6",
+    ],
+    gen_java: true,
+}
diff --git a/camera/metadata/3.8/Android.bp b/camera/metadata/3.8/Android.bp
new file mode 100644
index 0000000..30068d8
--- /dev/null
+++ b/camera/metadata/3.8/Android.bp
@@ -0,0 +1,18 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.camera.metadata@3.8",
+    root: "android.hardware",
+    srcs: [
+        "types.hal",
+    ],
+    interfaces: [
+        "android.hardware.camera.metadata@3.2",
+        "android.hardware.camera.metadata@3.3",
+        "android.hardware.camera.metadata@3.4",
+        "android.hardware.camera.metadata@3.5",
+        "android.hardware.camera.metadata@3.6",
+        "android.hardware.camera.metadata@3.7",
+    ],
+    gen_java: true,
+}
diff --git a/camera/metadata/3.8/types.hal b/camera/metadata/3.8/types.hal
index dcee775..b20af18 100644
--- a/camera/metadata/3.8/types.hal
+++ b/camera/metadata/3.8/types.hal
@@ -58,3 +58,11 @@
 /*
  * Enumeration definitions for the various entries that need them
  */
+
+/** android.control.videoStabilizationMode enumeration values added since v3.2
+ * @see ANDROID_CONTROL_VIDEO_STABILIZATION_MODE
+ */
+enum CameraMetadataEnumAndroidControlVideoStabilizationMode :
+        @3.2::CameraMetadataEnumAndroidControlVideoStabilizationMode {
+    ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION,
+};
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index ad3da48..d02547c 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -882,6 +882,7 @@
             camera_metadata* oldSessionParams, camera_metadata* newSessionParams);
 
     void verifyRequestTemplate(const camera_metadata_t* metadata, RequestTemplate requestTemplate);
+    static void overrideRotateAndCrop(::android::hardware::hidl_vec<uint8_t> *settings /*in/out*/);
 
     static bool isDepthOnly(const camera_metadata_t* staticMeta);
 
@@ -935,6 +936,9 @@
             camera_metadata_ro_entry* streamConfigs,
             camera_metadata_ro_entry* maxResolutionStreamConfigs,
             const camera_metadata_t* staticMetadata);
+    void getPrivacyTestPatternModes(
+            const camera_metadata_t* staticMetadata,
+            std::unordered_set<int32_t>* privacyTestPatternModes/*out*/);
     static bool isColorCamera(const camera_metadata_t *metadata);
 
     static V3_2::DataspaceFlags getDataspace(PixelFormat format);
@@ -4660,6 +4664,7 @@
                                                            settings = req;
                                                        });
         ASSERT_TRUE(ret.isOk());
+        overrideRotateAndCrop(&settings);
 
         hidl_handle buffer_handle;
         StreamBuffer outputBuffer;
@@ -4836,6 +4841,7 @@
         settings.setToExternal(
                 reinterpret_cast<uint8_t *> (const_cast<camera_metadata_t *> (settingsBuffer)),
                 get_camera_metadata_size(settingsBuffer));
+        overrideRotateAndCrop(&settings);
 
         free_camera_metadata(staticMeta);
         ret = session->close();
@@ -4913,6 +4919,7 @@
                 reinterpret_cast<uint8_t *> (const_cast<camera_metadata_t *> (
                         filteredSettingsBuffer)),
                 get_camera_metadata_size(filteredSettingsBuffer));
+        overrideRotateAndCrop(&camSettings[0].settings);
         camSettings[0].fmqSettingsSize = 0;
         camSettings[0].physicalCameraId = physicalDeviceId;
 
@@ -5070,6 +5077,7 @@
         settings.setToExternal(
                 reinterpret_cast<uint8_t*>(const_cast<camera_metadata_t*>(settingsBuffer)),
                 get_camera_metadata_size(settingsBuffer));
+        overrideRotateAndCrop(&settings);
 
         free_camera_metadata(staticMeta);
         ret = session->close();
@@ -5305,6 +5313,7 @@
             camera_metadata_t *metaBuffer = requestMeta.release();
             requestSettings[i].setToExternal(reinterpret_cast<uint8_t *> (metaBuffer),
                     get_camera_metadata_size(metaBuffer), true);
+            overrideRotateAndCrop(&requestSettings[i]);
 
             requests[i] = {frameNumber + i, 0 /* fmqSettingsSize */, requestSettings[i],
                 emptyInputBuffer, {outputBuffers[i]}};
@@ -5531,6 +5540,7 @@
             camera_metadata_t *metaBuffer = requestMeta.release();
             requestSettings[i].setToExternal(reinterpret_cast<uint8_t *> (metaBuffer),
                     get_camera_metadata_size(metaBuffer), true);
+            overrideRotateAndCrop(&requestSettings[i]);
 
             requests[i] = {frameNumber + i, 0 /* fmqSettingsSize */, requestSettings[i],
                 emptyInputBuffer, {outputBuffers[i]}};
@@ -5671,6 +5681,7 @@
                                                            settings = req;
                                                        });
         ASSERT_TRUE(ret.isOk());
+        overrideRotateAndCrop(&settings);
 
         ::android::hardware::hidl_vec<StreamBuffer> emptyOutputBuffers;
         StreamBuffer emptyInputBuffer = {-1, 0, nullptr, BufferStatus::ERROR, nullptr,
@@ -5755,6 +5766,7 @@
                                                            settings = req;
                                                        });
         ASSERT_TRUE(ret.isOk());
+        overrideRotateAndCrop(&settings);
 
         hidl_handle buffer_handle;
         if (useHalBufManager) {
@@ -6199,14 +6211,13 @@
         return;
     }
 
-    // Test that if more than one color cameras facing the same direction are
-    // supported, there must be at least one logical camera facing that
-    // direction.
+    // Test that if more than one rear-facing color camera is
+    // supported, there must be at least one rear-facing logical camera.
     hidl_vec<hidl_string> cameraDeviceNames = getCameraDeviceNames(mProvider);
-    // Front and back facing non-logical color cameras
-    std::set<std::string> frontColorCameras, rearColorCameras;
-    // Front and back facing logical cameras' physical camera Id sets
-    std::set<std::set<std::string>> frontPhysicalIds, rearPhysicalIds;
+    // Back facing non-logical color cameras
+    std::set<std::string> rearColorCameras;
+    // Back facing logical cameras' physical camera Id sets
+    std::set<std::set<std::string>> rearPhysicalIds;
     for (const auto& name : cameraDeviceNames) {
         std::string cameraId;
         int deviceVersion = getCameraDeviceVersionAndId(name, mProviderType, &cameraId);
@@ -6238,8 +6249,8 @@
                         return;
                     }
 
-                    // Check camera facing. Skip if facing is neither FRONT
-                    // nor BACK. If this is not a logical camera, only note down
+                    // Check camera facing. Skip if facing is not BACK.
+                    // If this is not a logical camera, only note down
                     // the camera ID, and skip.
                     camera_metadata_ro_entry entry;
                     int retcode = find_camera_metadata_ro_entry(
@@ -6248,18 +6259,12 @@
                     ASSERT_GT(entry.count, 0);
                     uint8_t facing = entry.data.u8[0];
                     bool isLogicalCamera = (isLogicalMultiCamera(metadata) == Status::OK);
-                    if (facing == ANDROID_LENS_FACING_FRONT) {
-                        if (!isLogicalCamera) {
-                            frontColorCameras.insert(cameraId);
-                            return;
-                        }
-                    } else if (facing == ANDROID_LENS_FACING_BACK) {
-                        if (!isLogicalCamera) {
-                            rearColorCameras.insert(cameraId);
-                            return;
-                        }
-                    } else {
-                        // Not FRONT or BACK facing. Skip.
+                    if (facing != ANDROID_LENS_FACING_BACK) {
+                        // Not BACK facing. Skip.
+                        return;
+                    }
+                    if (!isLogicalCamera) {
+                        rearColorCameras.insert(cameraId);
                         return;
                     }
 
@@ -6268,11 +6273,7 @@
                     std::unordered_set<std::string> physicalCameraIds;
                     Status s = getPhysicalCameraIds(metadata, &physicalCameraIds);
                     ASSERT_EQ(Status::OK, s);
-                    if (facing == ANDROID_LENS_FACING_FRONT) {
-                        frontPhysicalIds.emplace(physicalCameraIds.begin(), physicalCameraIds.end());
-                    } else {
-                        rearPhysicalIds.emplace(physicalCameraIds.begin(), physicalCameraIds.end());
-                    }
+                    rearPhysicalIds.emplace(physicalCameraIds.begin(), physicalCameraIds.end());
                     for (const auto& physicalId : physicalCameraIds) {
                         // Skip if the physicalId is publicly available
                         for (auto& deviceName : cameraDeviceNames) {
@@ -6299,11 +6300,7 @@
                                     (camera_metadata_t*)chars.data();
 
                             if (CameraHidlTest::isColorCamera(physicalMetadata)) {
-                                if (facing == ANDROID_LENS_FACING_FRONT) {
-                                    frontColorCameras.insert(physicalId);
-                                } else if (facing == ANDROID_LENS_FACING_BACK) {
-                                    rearColorCameras.insert(physicalId);
-                                }
+                                rearColorCameras.insert(physicalId);
                             }
                         });
                         ASSERT_TRUE(ret.isOk());
@@ -6321,20 +6318,9 @@
         }
     }
 
-    // If there are more than one color cameras facing one direction, a logical
-    // multi-camera must be defined consisting of all color cameras facing that
-    // direction.
-    if (frontColorCameras.size() > 1) {
-        bool hasFrontLogical = false;
-        for (const auto& physicalIds : frontPhysicalIds) {
-            if (std::includes(physicalIds.begin(), physicalIds.end(),
-                    frontColorCameras.begin(), frontColorCameras.end())) {
-                hasFrontLogical = true;
-                break;
-            }
-        }
-        ASSERT_TRUE(hasFrontLogical);
-    }
+    // If there are more than one rear-facing color camera, a logical
+    // multi-camera must be defined consisting of all rear-facing color
+    // cameras.
     if (rearColorCameras.size() > 1) {
         bool hasRearLogical = false;
         for (const auto& physicalIds : rearPhysicalIds) {
@@ -6779,6 +6765,25 @@
     ASSERT_TRUE(-ENOENT == retcode || 0 == retcode);
 }
 
+void CameraHidlTest::getPrivacyTestPatternModes(
+        const camera_metadata_t* staticMetadata,
+        std::unordered_set<int32_t>* privacyTestPatternModes/*out*/) {
+    ASSERT_NE(staticMetadata, nullptr);
+    ASSERT_NE(privacyTestPatternModes, nullptr);
+
+    camera_metadata_ro_entry entry;
+    int retcode = find_camera_metadata_ro_entry(
+            staticMetadata, ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES, &entry);
+    ASSERT_TRUE(0 == retcode);
+
+    for (auto i = 0; i < entry.count; i++) {
+        if (entry.data.i32[i] == ANDROID_SENSOR_TEST_PATTERN_MODE_SOLID_COLOR ||
+                entry.data.i32[i] == ANDROID_SENSOR_TEST_PATTERN_MODE_BLACK) {
+            privacyTestPatternModes->insert(entry.data.i32[i]);
+        }
+    }
+}
+
 // Select an appropriate dataspace given a specific pixel format.
 V3_2::DataspaceFlags CameraHidlTest::getDataspace(PixelFormat format) {
     switch (format) {
@@ -7833,6 +7838,16 @@
         ASSERT_TRUE(isUltraHighResCamera && !isMultiCamera);
         physicalIds.insert(cameraId);
     }
+
+    std::unordered_set<int32_t> physicalRequestKeyIDs;
+    rc = getSupportedKeys(const_cast<camera_metadata_t *>(metadata),
+            ANDROID_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS, &physicalRequestKeyIDs);
+    ASSERT_TRUE(Status::OK == rc);
+    bool hasTestPatternPhysicalRequestKey = physicalRequestKeyIDs.find(
+            ANDROID_SENSOR_TEST_PATTERN_MODE) != physicalRequestKeyIDs.end();
+    std::unordered_set<int32_t> privacyTestPatternModes;
+    getPrivacyTestPatternModes(metadata, &privacyTestPatternModes);
+
     // Map from image format to number of multi-resolution sizes for that format
     std::unordered_map<int32_t, size_t> multiResOutputFormatCounterMap;
     std::unordered_map<int32_t, size_t> multiResInputFormatCounterMap;
@@ -7854,6 +7869,7 @@
         camera_metadata_ro_entry physicalStreamConfigs;
         camera_metadata_ro_entry physicalMaxResolutionStreamConfigs;
         bool isUltraHighRes = false;
+        std::unordered_set<int32_t> subCameraPrivacyTestPatterns;
         if (isPublicId) {
             ::android::sp<::android::hardware::camera::device::V3_2::ICameraDevice> subDevice;
             Return<void> ret;
@@ -7884,6 +7900,8 @@
                         &physicalMultiResStreamConfigs, &physicalStreamConfigs,
                         &physicalMaxResolutionStreamConfigs, staticMetadata);
                 isUltraHighRes = isUltraHighResolution(staticMetadata);
+
+                getPrivacyTestPatternModes(staticMetadata, &subCameraPrivacyTestPatterns);
             });
             ASSERT_TRUE(ret.isOk());
         } else {
@@ -7910,6 +7928,7 @@
                                 &physicalMultiResStreamConfigs, &physicalStreamConfigs,
                                 &physicalMaxResolutionStreamConfigs, staticMetadata);
                         isUltraHighRes = isUltraHighResolution(staticMetadata);
+                        getPrivacyTestPatternModes(staticMetadata, &subCameraPrivacyTestPatterns);
                     });
             ASSERT_TRUE(ret.isOk());
 
@@ -7926,6 +7945,10 @@
             ASSERT_TRUE(ret.isOk());
         }
 
+        if (hasTestPatternPhysicalRequestKey) {
+            ASSERT_TRUE(privacyTestPatternModes == subCameraPrivacyTestPatterns);
+        }
+
         if (physicalMultiResStreamConfigs.count > 0) {
             ASSERT_GE(deviceVersion, CAMERA_DEVICE_API_VERSION_3_7);
             ASSERT_EQ(physicalMultiResStreamConfigs.count % 4, 0);
@@ -8935,6 +8958,25 @@
     }
 }
 
+void CameraHidlTest::overrideRotateAndCrop(
+        ::android::hardware::hidl_vec<uint8_t> *settings /*in/out*/) {
+    if (settings == nullptr) {
+        return;
+    }
+
+    ::android::hardware::camera::common::V1_0::helper::CameraMetadata requestMeta;
+    requestMeta.append(reinterpret_cast<camera_metadata_t *> (settings->data()));
+    auto entry = requestMeta.find(ANDROID_SCALER_ROTATE_AND_CROP);
+    if ((entry.count > 0) && (entry.data.u8[0] == ANDROID_SCALER_ROTATE_AND_CROP_AUTO)) {
+        uint8_t disableRotateAndCrop = ANDROID_SCALER_ROTATE_AND_CROP_NONE;
+        requestMeta.update(ANDROID_SCALER_ROTATE_AND_CROP, &disableRotateAndCrop, 1);
+        settings->releaseData();
+        camera_metadata_t *metaBuffer = requestMeta.release();
+        settings->setToExternal(reinterpret_cast<uint8_t *> (metaBuffer),
+                get_camera_metadata_size(metaBuffer), true);
+    }
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CameraHidlTest);
 INSTANTIATE_TEST_SUITE_P(
         PerInstance, CameraHidlTest,
diff --git a/cas/1.0/vts/functional/OWNERS b/cas/1.0/vts/functional/OWNERS
index aec93b0..7d8c2ee 100644
--- a/cas/1.0/vts/functional/OWNERS
+++ b/cas/1.0/vts/functional/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 1344
-quxiangfang@google.com
+include ../../../1.2/vts/functional/OWNERS
diff --git a/cas/1.1/vts/functional/OWNERS b/cas/1.1/vts/functional/OWNERS
index 29246ed..7d8c2ee 100644
--- a/cas/1.1/vts/functional/OWNERS
+++ b/cas/1.1/vts/functional/OWNERS
@@ -1,3 +1,2 @@
-nchalko@google.com
-chz@google.com
-quxiangfang@google.com
+# Bug component: 1344
+include ../../../1.2/vts/functional/OWNERS
diff --git a/cas/1.2/vts/functional/OWNERS b/cas/1.2/vts/functional/OWNERS
index 29246ed..4c55752 100644
--- a/cas/1.2/vts/functional/OWNERS
+++ b/cas/1.2/vts/functional/OWNERS
@@ -1,3 +1,3 @@
-nchalko@google.com
-chz@google.com
+# Bug component: 1344
 quxiangfang@google.com
+hgchen@google.com
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 92f9263..5d92304 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -125,6 +125,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.biometrics.fingerprint</name>
+        <version>2</version>
         <interface>
             <name>IFingerprint</name>
             <instance>default</instance>
@@ -140,7 +141,7 @@
     </hal>
     <hal format="hidl" optional="true">
         <name>android.hardware.bluetooth.audio</name>
-        <version>2.0-1</version>
+        <version>2.0-2</version>
         <interface>
             <name>IBluetoothAudioProvidersFactory</name>
             <instance>default</instance>
@@ -247,6 +248,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.gnss</name>
+        <version>2</version>
         <interface>
             <name>IGnss</name>
             <instance>default</instance>
@@ -298,6 +300,15 @@
             <instance>default</instance>
         </interface>
     </hal>
+    <!-- TODO(b/177269435): require health AIDL HAL and deprecate HIDL HAL -->
+    <hal format="aidl" optional="true">
+        <name>android.hardware.health</name>
+        <version>1</version>
+        <interface>
+            <name>IHealth</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.health.storage</name>
         <version>1</version>
diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp
index 1def738..2aa4bb2 100644
--- a/compatibility_matrices/exclude/fcm_exclude.cpp
+++ b/compatibility_matrices/exclude/fcm_exclude.cpp
@@ -51,6 +51,7 @@
             "android.hardware.media.bufferpool@2.0",
             "android.hardware.radio.config@1.2",
             // AIDL
+            "android.hardware.audio.common",
             "android.hardware.biometrics.common",
             "android.hardware.common",
             "android.hardware.common.fmq",
diff --git a/contexthub/aidl/default/ContextHub.cpp b/contexthub/aidl/default/ContextHub.cpp
index 1b56608..1fbccc5 100644
--- a/contexthub/aidl/default/ContextHub.cpp
+++ b/contexthub/aidl/default/ContextHub.cpp
@@ -21,38 +21,50 @@
 namespace hardware {
 namespace contexthub {
 
-// TODO(b/194285834): Implement AIDL HAL
+::ndk::ScopedAStatus ContextHub::getContextHubs(std::vector<ContextHubInfo>* out_contextHubInfos) {
+    ContextHubInfo hub = {};
+    hub.name = "Mock Context Hub";
+    hub.vendor = "AOSP";
+    hub.toolchain = "n/a";
+    hub.id = kMockHubId;
+    hub.peakMips = 1;
+    hub.maxSupportedMessageLengthBytes = 4096;
+    hub.chrePlatformId = UINT64_C(0x476f6f6754000000);
+    hub.chreApiMajorVersion = 1;
+    hub.chreApiMinorVersion = 6;
 
-::ndk::ScopedAStatus ContextHub::getContextHubs(
-        std::vector<ContextHubInfo>* /* out_contextHubInfos */) {
+    out_contextHubInfos->push_back(hub);
+
     return ndk::ScopedAStatus::ok();
 }
 
+// We don't expose any nanoapps for the default impl, therefore all nanoapp-related APIs fail.
 ::ndk::ScopedAStatus ContextHub::loadNanoapp(int32_t /* in_contextHubId */,
                                              const NanoappBinary& /* in_appBinary */,
-                                             int32_t /* in_transactionId */,
-                                             bool* /* _aidl_return */) {
+                                             int32_t /* in_transactionId */, bool* _aidl_return) {
+    *_aidl_return = false;
     return ndk::ScopedAStatus::ok();
 }
 
 ::ndk::ScopedAStatus ContextHub::unloadNanoapp(int32_t /* in_contextHubId */,
                                                int64_t /* in_appId */,
-                                               int32_t /* in_transactionId */,
-                                               bool* /* _aidl_return */) {
+                                               int32_t /* in_transactionId */, bool* _aidl_return) {
+    *_aidl_return = false;
     return ndk::ScopedAStatus::ok();
 }
 
 ::ndk::ScopedAStatus ContextHub::disableNanoapp(int32_t /* in_contextHubId */,
                                                 int64_t /* in_appId */,
                                                 int32_t /* in_transactionId */,
-                                                bool* /* _aidl_return */) {
+                                                bool* _aidl_return) {
+    *_aidl_return = false;
     return ndk::ScopedAStatus::ok();
 }
 
 ::ndk::ScopedAStatus ContextHub::enableNanoapp(int32_t /* in_contextHubId */,
                                                int64_t /* in_appId */,
-                                               int32_t /* in_transactionId */,
-                                               bool* /* _aidl_return */) {
+                                               int32_t /* in_transactionId */, bool* _aidl_return) {
+    *_aidl_return = false;
     return ndk::ScopedAStatus::ok();
 }
 
@@ -60,20 +72,42 @@
     return ndk::ScopedAStatus::ok();
 }
 
-::ndk::ScopedAStatus ContextHub::queryNanoapps(int32_t /* in_contextHubId */,
-                                               bool* /* _aidl_return */) {
+::ndk::ScopedAStatus ContextHub::queryNanoapps(int32_t in_contextHubId, bool* _aidl_return) {
+    if (in_contextHubId == kMockHubId && mCallback != nullptr) {
+        std::vector<NanoappInfo> nanoapps;
+        mCallback->handleNanoappInfo(nanoapps);
+        *_aidl_return = true;
+    } else {
+        *_aidl_return = false;
+    }
+
     return ndk::ScopedAStatus::ok();
 }
 
-::ndk::ScopedAStatus ContextHub::registerCallback(
-        int32_t /* in_contextHubId */, const std::shared_ptr<IContextHubCallback>& /* in_cb */,
-        bool* /* _aidl_return */) {
+::ndk::ScopedAStatus ContextHub::registerCallback(int32_t in_contextHubId,
+                                                  const std::shared_ptr<IContextHubCallback>& in_cb,
+                                                  bool* _aidl_return) {
+    if (in_contextHubId == kMockHubId) {
+        mCallback = in_cb;
+        *_aidl_return = true;
+    } else {
+        *_aidl_return = false;
+    }
     return ndk::ScopedAStatus::ok();
 }
 
-::ndk::ScopedAStatus ContextHub::sendMessageToHub(int32_t /* in_contextHubId */,
+::ndk::ScopedAStatus ContextHub::sendMessageToHub(int32_t in_contextHubId,
                                                   const ContextHubMessage& /* in_message */,
-                                                  bool* /* _aidl_return */) {
+                                                  bool* _aidl_return) {
+    if (in_contextHubId == kMockHubId) {
+        // Return true here to indicate that the HAL has accepted the message.
+        // Successful delivery of the message to a nanoapp should be handled at
+        // a higher level protocol.
+        *_aidl_return = true;
+    } else {
+        *_aidl_return = false;
+    }
+
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/contexthub/aidl/default/include/contexthub-impl/ContextHub.h b/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
index 980cee5..0dbb61b 100644
--- a/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
+++ b/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
@@ -41,6 +41,10 @@
     ::ndk::ScopedAStatus sendMessageToHub(int32_t in_contextHubId,
                                           const ContextHubMessage& in_message,
                                           bool* _aidl_return) override;
+
+  private:
+    static constexpr uint32_t kMockHubId = 0;
+    std::shared_ptr<IContextHubCallback> mCallback;
 };
 
 }  // namespace contexthub
diff --git a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
index 3601f13..4b0d60f 100644
--- a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
+++ b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
@@ -74,18 +74,36 @@
         EXPECT_GT(hub.peakMips, 0);
         EXPECT_GT(hub.chrePlatformId, 0);
         EXPECT_GT(hub.chreApiMajorVersion, 0);
-        EXPECT_GT(hub.chreApiMinorVersion, 0);
-        EXPECT_GT(hub.chrePatchVersion, 0);
+        EXPECT_GE(hub.chreApiMinorVersion, 0);
+        EXPECT_GE(hub.chrePatchVersion, 0);
 
         // Minimum 128 byte MTU as required by CHRE API v1.0
         EXPECT_GE(hub.maxSupportedMessageLengthBytes, UINT32_C(128));
     }
 }
 
+class EmptyContextHubCallback : public android::hardware::contexthub::BnContextHubCallback {
+  public:
+    Status handleNanoappInfo(const std::vector<NanoappInfo>& /* appInfo */) override {
+        return Status::ok();
+    }
+
+    Status handleContextHubMessage(const ContextHubMessage& /* msg */,
+                                   const std::vector<String16>& /* msgContentPerms */) override {
+        return Status::ok();
+    }
+
+    Status handleContextHubAsyncEvent(AsyncEventType /* evt */) override { return Status::ok(); }
+
+    Status handleTransactionResult(int32_t /* transactionId */, bool /* success */) override {
+        return Status::ok();
+    }
+};
+
 TEST_P(ContextHubAidl, TestRegisterCallback) {
     bool success;
-    ASSERT_TRUE(contextHub->registerCallback(getHubId(), new IContextHubCallbackDefault(), &success)
-                        .isOk());
+    sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
+    ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb, &success).isOk());
     ASSERT_TRUE(success);
 }
 
@@ -263,8 +281,8 @@
     // In VTS, we only test that sending the values doesn't cause things to blow up - GTS tests
     // verify the expected E2E behavior in CHRE
     bool success;
-    ASSERT_TRUE(contextHub->registerCallback(getHubId(), new IContextHubCallbackDefault(), &success)
-                        .isOk());
+    sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
+    ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb, &success).isOk());
     ASSERT_TRUE(success);
 
     ASSERT_TRUE(contextHub->onSettingChanged(setting, true /* enabled */).isOk());
diff --git a/gnss/1.1/default/Android.bp b/gnss/1.1/default/Android.bp
index 3c9c29a..a73182e 100644
--- a/gnss/1.1/default/Android.bp
+++ b/gnss/1.1/default/Android.bp
@@ -27,7 +27,7 @@
         "android.hardware.gnss@2.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@1.0",
-        "android.hardware.gnss-V1-ndk",
+        "android.hardware.gnss-V2-ndk",
     ],
     static_libs: [
         "android.hardware.gnss@common-default-lib",
diff --git a/gnss/2.0/default/Android.bp b/gnss/2.0/default/Android.bp
index 695246a..769e8ae 100644
--- a/gnss/2.0/default/Android.bp
+++ b/gnss/2.0/default/Android.bp
@@ -50,7 +50,7 @@
         "android.hardware.gnss@2.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@1.0",
-        "android.hardware.gnss-V1-ndk",
+        "android.hardware.gnss-V2-ndk",
     ],
     static_libs: [
         "android.hardware.gnss@common-default-lib",
diff --git a/gnss/2.1/default/Android.bp b/gnss/2.1/default/Android.bp
index c46c735..2979f5c 100644
--- a/gnss/2.1/default/Android.bp
+++ b/gnss/2.1/default/Android.bp
@@ -44,7 +44,7 @@
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
-        "android.hardware.gnss-V1-ndk",
+        "android.hardware.gnss-V2-ndk",
     ],
     static_libs: [
         "android.hardware.gnss@common-default-lib",
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssLocation.aidl
similarity index 69%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssLocation.aidl
index 2b872ab..54c126c 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/GnssLocation.aidl
@@ -31,14 +31,27 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.gnss;
+@VintfStability
+parcelable GnssLocation {
+  int gnssLocationFlags;
+  double latitudeDegrees;
+  double longitudeDegrees;
+  double altitudeMeters;
+  double speedMetersPerSec;
+  double bearingDegrees;
+  double horizontalAccuracyMeters;
+  double verticalAccuracyMeters;
+  double speedAccuracyMetersPerSecond;
+  double bearingAccuracyDegrees;
+  long timestampMillis;
+  android.hardware.gnss.ElapsedRealtime elapsedRealtime;
+  const int HAS_LAT_LONG = 1;
+  const int HAS_ALTITUDE = 2;
+  const int HAS_SPEED = 4;
+  const int HAS_BEARING = 8;
+  const int HAS_HORIZONTAL_ACCURACY = 16;
+  const int HAS_VERTICAL_ACCURACY = 32;
+  const int HAS_SPEED_ACCURACY = 64;
+  const int HAS_BEARING_ACCURACY = 128;
 }
diff --git a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl
index f93b496..52276b4 100644
--- a/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnss.aidl
@@ -36,10 +36,11 @@
 interface IGnss {
   void setCallback(in android.hardware.gnss.IGnssCallback callback);
   void close();
-  android.hardware.gnss.IGnssPsds getExtensionPsds();
+  @nullable android.hardware.gnss.IGnssPsds getExtensionPsds();
   android.hardware.gnss.IGnssConfiguration getExtensionGnssConfiguration();
   android.hardware.gnss.IGnssMeasurementInterface getExtensionGnssMeasurement();
   android.hardware.gnss.IGnssPowerIndication getExtensionGnssPowerIndication();
+  @nullable android.hardware.gnss.IGnssBatching getExtensionGnssBatching();
   const int ERROR_INVALID_ARGUMENT = 1;
   const int ERROR_ALREADY_INIT = 2;
   const int ERROR_GENERIC = 3;
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatching.aidl
similarity index 84%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatching.aidl
index 2b872ab..492edc3 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatching.aidl
@@ -31,14 +31,14 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.gnss;
+@VintfStability
+interface IGnssBatching {
+  void init(in android.hardware.gnss.IGnssBatchingCallback callback);
+  int getBatchSize();
+  void start(in long periodNanos, in int flags);
+  void flush();
+  void stop();
+  void cleanup();
+  const int WAKEUP_ON_FIFO_FULL = 1;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatchingCallback.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatchingCallback.aidl
index 2b872ab..427137a 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/gnss/aidl/aidl_api/android.hardware.gnss/current/android/hardware/gnss/IGnssBatchingCallback.aidl
@@ -31,14 +31,8 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.gnss;
+@VintfStability
+interface IGnssBatchingCallback {
+  void gnssLocationBatchCb(in android.hardware.gnss.GnssLocation[] locations);
 }
diff --git a/gnss/aidl/android/hardware/gnss/GnssLocation.aidl b/gnss/aidl/android/hardware/gnss/GnssLocation.aidl
new file mode 100644
index 0000000..25aea4d
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/GnssLocation.aidl
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package android.hardware.gnss;
+
+import android.hardware.gnss.ElapsedRealtime;
+
+/** Represents a location. */
+@VintfStability
+parcelable GnssLocation {
+    /** Bit mask to indicate GnssLocation has valid latitude and longitude. */
+    const int HAS_LAT_LONG = 0x0001;
+    /** Bit mask to indicate GnssLocation has valid altitude. */
+    const int HAS_ALTITUDE = 0x0002;
+    /** Bit mask to indicate GnssLocation has valid speed. */
+    const int HAS_SPEED = 0x0004;
+    /** Bit mask to indicate GnssLocation has valid bearing. */
+    const int HAS_BEARING = 0x0008;
+    /** Bit mask to indicate GnssLocation has valid horizontal accuracy. */
+    const int HAS_HORIZONTAL_ACCURACY = 0x0010;
+    /** Bit mask to indicate GnssLocation has valid vertical accuracy. */
+    const int HAS_VERTICAL_ACCURACY = 0x0020;
+    /** Bit mask to indicate GnssLocation has valid speed accuracy. */
+    const int HAS_SPEED_ACCURACY = 0x0040;
+    /** Bit mask to indicate GnssLocation has valid bearing accuracy. */
+    const int HAS_BEARING_ACCURACY = 0x0080;
+
+    /** A bit field of flags indicating the validity of the fields in this GnssLocation. */
+    int gnssLocationFlags;
+
+    /** Represents latitude in degrees. */
+    double latitudeDegrees;
+
+    /** Represents longitude in degrees. */
+    double longitudeDegrees;
+
+    /** Represents altitude in meters above the WGS 84 reference ellipsoid. */
+    double altitudeMeters;
+
+    /** Represents speed in meters per second. */
+    double speedMetersPerSec;
+
+    /** Represents heading in degrees. */
+    double bearingDegrees;
+
+    /**
+     * Represents expected horizontal position accuracy, radial, in meters (68% confidence).
+     */
+    double horizontalAccuracyMeters;
+
+    /**
+     * Represents expected vertical position accuracy in meters (68% confidence).
+     */
+    double verticalAccuracyMeters;
+
+    /**
+     * Represents expected speed accuracy in meter per seconds (68% confidence).
+     */
+    double speedAccuracyMetersPerSecond;
+
+    /**
+     * Represents expected bearing accuracy in degrees (68% confidence).
+     */
+    double bearingAccuracyDegrees;
+
+    /** Timestamp for the location fix in milliseconds since January 1, 1970. */
+    long timestampMillis;
+
+    /**
+     * Timing information of the GNSS location synchronized with SystemClock.elapsedRealtimeNanos()
+     * clock.
+     *
+     * This clock information can be obtained from SystemClock.elapsedRealtimeNanos(), when the GNSS
+     * is attached straight to the AP/SOC. When it is attached to a separate module the timestamp
+     * needs to be estimated by syncing the notion of time via PTP or some other mechanism.
+     */
+    ElapsedRealtime elapsedRealtime;
+}
diff --git a/gnss/aidl/android/hardware/gnss/IGnss.aidl b/gnss/aidl/android/hardware/gnss/IGnss.aidl
index f99b512..b12fb82 100644
--- a/gnss/aidl/android/hardware/gnss/IGnss.aidl
+++ b/gnss/aidl/android/hardware/gnss/IGnss.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.gnss;
 
+import android.hardware.gnss.IGnssBatching;
 import android.hardware.gnss.IGnssCallback;
 import android.hardware.gnss.IGnssConfiguration;
 import android.hardware.gnss.IGnssMeasurementInterface;
@@ -27,9 +28,8 @@
  */
 @VintfStability
 interface IGnss {
-
     /**
-     * All GNSS Binder calls may return a ServiceSpecificException with the following error
+     * All GNSS binder calls may return a ServiceSpecificException with the following error
      * codes.
      */
     const int ERROR_INVALID_ARGUMENT = 1;
@@ -73,11 +73,9 @@
     /**
      * This method returns the IGnssPsds interface.
      *
-     * This method must return non-null.
-     *
      * @return Handle to the IGnssPsds interface.
      */
-    IGnssPsds getExtensionPsds();
+    @nullable IGnssPsds getExtensionPsds();
 
     /**
      * This method returns the IGnssConfiguration interface.
@@ -89,7 +87,7 @@
     IGnssConfiguration getExtensionGnssConfiguration();
 
     /**
-     * This methods returns the IGnssMeasurementInterface interface.
+     * This method returns the IGnssMeasurementInterface interface.
      *
      * This method must return non-null.
      *
@@ -105,4 +103,11 @@
      * @return Handle to the IGnssPowerIndication interface.
      */
     IGnssPowerIndication getExtensionGnssPowerIndication();
+
+    /**
+     * This method returns the IGnssBatching interface.
+     *
+     * @return Handle to the IGnssBatching interface.
+     */
+    @nullable IGnssBatching getExtensionGnssBatching();
 }
diff --git a/gnss/aidl/android/hardware/gnss/IGnssBatching.aidl b/gnss/aidl/android/hardware/gnss/IGnssBatching.aidl
new file mode 100644
index 0000000..0d48ee1
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/IGnssBatching.aidl
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+package android.hardware.gnss;
+
+import android.hardware.gnss.IGnssBatchingCallback;
+
+/**
+ * Extended interface for GNSS Batching support.
+ *
+ * If this interface is supported, this batching request must be able to run in parallel with, or
+ * without, non-batched location requested by the IGnss start() & stop() - i.e. both requests must
+ * be handled independently, and not interfere with each other.
+ *
+ * For example, if a 1Hz continuous output is underway on the IGnssCallback, due to an IGnss start()
+ * operation, and then a IGnssBatching start() is called for a location every 10 seconds, the newly
+ * added batching request must not disrupt the 1Hz continuous location output on the IGnssCallback.
+ *
+ * As with GNSS Location outputs, source of location must be GNSS satellite measurements, optionally
+ * using interial and baro sensors to improve relative motion filtering. No additional absolute
+ * positioning information, such as WiFi derived location, may be mixed with the GNSS information.
+ */
+@VintfStability
+interface IGnssBatching {
+    /**
+     * Bit mask indicating batching supports wake up and flush when FIFO is full.
+     *
+     * If this flag is set, the hardware implementation must wake up the application processor when
+     * the FIFO is full, and call IGnssBatchingCallback to return the locations.
+     *
+     * If the flag is not set, the hardware implementation must drop the oldest data when the FIFO
+     * is full.
+     */
+    const int WAKEUP_ON_FIFO_FULL = 0x01;
+
+    /**
+     * Open the interface and provides the callback routines to the implementation of this
+     * interface.
+     *
+     * @param callback Callback interface for IGnssBatching.
+     */
+    void init(in IGnssBatchingCallback callback);
+
+    /**
+     * Return the batch size (in number of GnssLocation objects) available in this hardware
+     * implementation.
+     *
+     * If the available size is variable, for example, based on other operations consuming memory,
+     * this is the minimum size guaranteed to be available for batching operations.
+     *
+     * This may, for example, be used by the client, to decide on the batching interval and whether
+     * the AP should be woken up or not.
+     *
+     * @return the number of location objects supported per batch
+     */
+    int getBatchSize();
+
+    /**
+     * Start batching locations. This API is primarily used when the AP is asleep and the device can
+     * batch locations in the hardware.
+     *
+     * The implementation must invoke IGnssBatchingCallback, provided in init(), to return the
+     * location.
+     *
+     * When the buffer is full and WAKEUP_ON_FIFO_FULL is used, IGnssBatchingCallback must be called
+     * to return the locations.
+     *
+     * When the buffer is full and WAKEUP_ON_FIFO_FULL is not set, the oldest location object is
+     * dropped. In this case the AP must not be woken up. The AP would then generally be responsible
+     * for using flushBatchedLocation to explicitly ask for the location as needed, to avoid it
+     * being dropped.
+     *
+     * @param periodNanos  Time interval between samples in the location batch, in nanoseconds
+     * @param flags  A bitfield of flags (WAKEUP_ON_FIFO_FULL) indicating the batching behavior
+     */
+    void start(in long periodNanos, in int flags);
+
+    /**
+     * Retrieve all batched locations currently stored.
+     *
+     * The implementation must invoke IGnssBatchingCallback, provided in init(), to return the
+     * location.
+     *
+     * IGnssBatchingCallback must be called in response, even if there are no locations to flush
+     * (in which case the Location vector must be empty).
+     *
+     * Subsequent calls to flush must not return any of the locations returned in this call.
+     */
+    void flush();
+
+    /**
+     * Stop batching.
+     */
+    void stop();
+
+    /**
+     * Closes the interface. If any batch operations are in progress, they must be stopped.  If any
+     * locations are in the hardware batch, they must be deleted (and not sent via callback.)
+     *
+     * init() may be called again, after this, if the interface is to be restored
+     */
+    void cleanup();
+}
diff --git a/gnss/aidl/android/hardware/gnss/IGnssBatchingCallback.aidl b/gnss/aidl/android/hardware/gnss/IGnssBatchingCallback.aidl
new file mode 100644
index 0000000..b1bfc57
--- /dev/null
+++ b/gnss/aidl/android/hardware/gnss/IGnssBatchingCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.gnss;
+
+import android.hardware.gnss.GnssLocation;
+
+/** The callback interface to report batched GNSS locations from the HAL. */
+@VintfStability
+interface IGnssBatchingCallback {
+    /**
+     * Called when a batch of locations is output, by various means, including
+     * a flush request, as well as the buffer becoming full (if appropriate option
+     * is set.)
+     *
+     * All locations returned by this callback must be cleared from the hardware
+     * buffer, such the sequential calls of this callback do not return any
+     * redundant locations.  (Same lat/lon, at a new time, is acceptable.)
+     *
+     * @param locations A list of GNSS Locations
+     */
+    void gnssLocationBatchCb(in GnssLocation[] locations);
+}
diff --git a/gnss/aidl/default/Android.bp b/gnss/aidl/default/Android.bp
index c028dd7..892ad15 100644
--- a/gnss/aidl/default/Android.bp
+++ b/gnss/aidl/default/Android.bp
@@ -52,10 +52,11 @@
         "android.hardware.gnss.measurement_corrections@1.1",
         "android.hardware.gnss.measurement_corrections@1.0",
         "android.hardware.gnss.visibility_control@1.0",
-        "android.hardware.gnss-V1-ndk",
+        "android.hardware.gnss-V2-ndk",
     ],
     srcs: [
         "Gnss.cpp",
+        "GnssBatching.cpp",
         "GnssHidlHal.cpp",
         "GnssPowerIndication.cpp",
         "GnssPsds.cpp",
diff --git a/gnss/aidl/default/Gnss.cpp b/gnss/aidl/default/Gnss.cpp
index 6061eec..fbfa2bb 100644
--- a/gnss/aidl/default/Gnss.cpp
+++ b/gnss/aidl/default/Gnss.cpp
@@ -18,6 +18,7 @@
 
 #include "Gnss.h"
 #include <log/log.h>
+#include "GnssBatching.h"
 #include "GnssConfiguration.h"
 #include "GnssMeasurementInterface.h"
 #include "GnssPsds.h"
@@ -88,4 +89,11 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Gnss::getExtensionGnssBatching(std::shared_ptr<IGnssBatching>* iGnssBatching) {
+    ALOGD("Gnss::getExtensionGnssBatching");
+
+    *iGnssBatching = SharedRefBase::make<GnssBatching>();
+    return ndk::ScopedAStatus::ok();
+}
+
 }  // namespace aidl::android::hardware::gnss
diff --git a/gnss/aidl/default/Gnss.h b/gnss/aidl/default/Gnss.h
index 76ebe4d..3959ef8 100644
--- a/gnss/aidl/default/Gnss.h
+++ b/gnss/aidl/default/Gnss.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <aidl/android/hardware/gnss/BnGnss.h>
+#include <aidl/android/hardware/gnss/BnGnssBatching.h>
 #include <aidl/android/hardware/gnss/BnGnssConfiguration.h>
 #include <aidl/android/hardware/gnss/BnGnssMeasurementInterface.h>
 #include <aidl/android/hardware/gnss/BnGnssPowerIndication.h>
@@ -37,6 +38,8 @@
             std::shared_ptr<IGnssPowerIndication>* iGnssPowerIndication) override;
     ndk::ScopedAStatus getExtensionGnssMeasurement(
             std::shared_ptr<IGnssMeasurementInterface>* iGnssMeasurement) override;
+    ndk::ScopedAStatus getExtensionGnssBatching(
+            std::shared_ptr<IGnssBatching>* iGnssBatching) override;
 
     std::shared_ptr<GnssConfiguration> mGnssConfiguration;
     std::shared_ptr<GnssPowerIndication> mGnssPowerIndication;
diff --git a/gnss/aidl/default/GnssBatching.cpp b/gnss/aidl/default/GnssBatching.cpp
new file mode 100644
index 0000000..b8be5e5
--- /dev/null
+++ b/gnss/aidl/default/GnssBatching.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GnssBatchingAidl"
+
+#include "GnssBatching.h"
+#include <aidl/android/hardware/gnss/BnGnss.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+#include "Utils.h"
+
+namespace aidl::android::hardware::gnss {
+
+using namespace ::android::hardware::gnss;
+
+constexpr int BATCH_SIZE = 10;
+
+std::shared_ptr<IGnssBatchingCallback> GnssBatching::sCallback = nullptr;
+
+GnssBatching::GnssBatching()
+    : mMinIntervalMs(1000),
+      mWakeUpOnFifoFull(false),
+      mBatchedLocations(std::vector<GnssLocation>()) {}
+GnssBatching::~GnssBatching() {
+    cleanup();
+}
+
+ndk::ScopedAStatus GnssBatching::init(const std::shared_ptr<IGnssBatchingCallback>& callback) {
+    ALOGD("init");
+    std::unique_lock<std::mutex> lock(mMutex);
+    sCallback = callback;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus GnssBatching::getBatchSize(int* size) {
+    ALOGD("getBatchingSize");
+    *size = BATCH_SIZE;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus GnssBatching::start(int64_t periodNanos, int flags) {
+    ALOGD("start: periodNanos=%" PRId64 ", flags=%d", periodNanos, flags);
+    if (mIsActive) {
+        ALOGW("Gnss has started. Restarting...");
+        stop();
+    }
+
+    mWakeUpOnFifoFull = (flags & IGnssBatching::WAKEUP_ON_FIFO_FULL) ? true : false;
+    // mMinIntervalMs is not smaller than 1 sec
+    periodNanos = (periodNanos < 1e9) ? 1e9 : periodNanos;
+    mMinIntervalMs = periodNanos / 1e6;
+
+    mIsActive = true;
+    mThread = std::thread([this]() {
+        while (mIsActive == true) {
+            const auto location = common::Utils::getMockLocation();
+            this->batchLocation(location);
+            std::this_thread::sleep_for(std::chrono::milliseconds(mMinIntervalMs));
+        }
+    });
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus GnssBatching::flush() {
+    ALOGD("flush");
+    std::vector<GnssLocation> copy = std::vector<GnssLocation>(mBatchedLocations);
+    ndk::ScopedAStatus status;
+    if (sCallback != nullptr) {
+        sCallback->gnssLocationBatchCb(copy);
+        status = ndk::ScopedAStatus::ok();
+    } else {
+        ALOGE("GnssBatchingCallback is null. flush() failed.");
+        status = ndk::ScopedAStatus::fromServiceSpecificError(IGnss::ERROR_GENERIC);
+    }
+    mBatchedLocations.clear();
+    return status;
+}
+
+ndk::ScopedAStatus GnssBatching::stop() {
+    ALOGD("stop");
+    // Do not call flush() at stop()
+    mIsActive = false;
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus GnssBatching::cleanup() {
+    ALOGD("cleanup");
+    std::unique_lock<std::mutex> lock(mMutex);
+    if (mIsActive) {
+        stop();
+    }
+    flush();
+
+    sCallback = nullptr;
+    return ndk::ScopedAStatus::ok();
+}
+
+void GnssBatching::batchLocation(const GnssLocation& location) {
+    if (mBatchedLocations.size() > BATCH_SIZE) {
+        mBatchedLocations.erase(mBatchedLocations.begin());
+    }
+    mBatchedLocations.push_back(location);
+    if (mWakeUpOnFifoFull && mBatchedLocations.size() == BATCH_SIZE) {
+        flush();
+    }
+}
+
+}  // namespace aidl::android::hardware::gnss
diff --git a/gnss/aidl/default/GnssBatching.h b/gnss/aidl/default/GnssBatching.h
new file mode 100644
index 0000000..7cd6e85
--- /dev/null
+++ b/gnss/aidl/default/GnssBatching.h
@@ -0,0 +1,53 @@
+/*
+ * 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 <aidl/android/hardware/gnss/BnGnssBatching.h>
+#include <atomic>
+#include <thread>
+
+namespace aidl::android::hardware::gnss {
+
+struct GnssBatching : public BnGnssBatching {
+  public:
+    GnssBatching();
+    ~GnssBatching();
+    ndk::ScopedAStatus init(const std::shared_ptr<IGnssBatchingCallback>& callback) override;
+    ndk::ScopedAStatus getBatchSize(int* size) override;
+    ndk::ScopedAStatus start(int64_t periodNanos, int flags) override;
+    ndk::ScopedAStatus flush() override;
+    ndk::ScopedAStatus stop() override;
+    ndk::ScopedAStatus cleanup() override;
+
+  private:
+    void batchLocation(const GnssLocation&);
+
+    // Guarded by mMutex
+    static std::shared_ptr<IGnssBatchingCallback> sCallback;
+
+    std::thread mThread;
+    std::atomic<bool> mIsActive;
+    std::atomic<long> mMinIntervalMs;
+    std::atomic<bool> mWakeUpOnFifoFull;
+
+    // Synchronization lock for sCallback
+    mutable std::mutex mMutex;
+
+    std::vector<GnssLocation> mBatchedLocations;
+};
+
+}  // namespace aidl::android::hardware::gnss
diff --git a/gnss/aidl/default/gnss-default.xml b/gnss/aidl/default/gnss-default.xml
index 2b06cd2..7449310 100644
--- a/gnss/aidl/default/gnss-default.xml
+++ b/gnss/aidl/default/gnss-default.xml
@@ -1,6 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.gnss</name>
+        <version>2</version>
         <interface>
             <name>IGnss</name>
             <instance>default</instance>
diff --git a/gnss/aidl/vts/Android.bp b/gnss/aidl/vts/Android.bp
index 838849d..6096d4d 100644
--- a/gnss/aidl/vts/Android.bp
+++ b/gnss/aidl/vts/Android.bp
@@ -30,6 +30,7 @@
     srcs: [
         "gnss_hal_test.cpp",
         "gnss_hal_test_cases.cpp",
+        "GnssBatchingCallback.cpp",
         "GnssCallbackAidl.cpp",
         "GnssMeasurementCallbackAidl.cpp",
         "GnssPowerIndicationCallback.cpp",
@@ -43,7 +44,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.gnss-V1-cpp",
+        "android.hardware.gnss-V2-cpp",
         "android.hardware.gnss@common-vts-lib",
     ],
     test_suites: [
diff --git a/gnss/aidl/vts/GnssBatchingCallback.cpp b/gnss/aidl/vts/GnssBatchingCallback.cpp
new file mode 100644
index 0000000..2da3b12
--- /dev/null
+++ b/gnss/aidl/vts/GnssBatchingCallback.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "GnssBatchingCallback.h"
+#include <inttypes.h>
+#include <log/log.h>
+
+android::binder::Status GnssBatchingCallback::gnssLocationBatchCb(
+        const std::vector<android::hardware::gnss::GnssLocation>& locations) {
+    ALOGI("Batched locations received with size=%d", (int)locations.size());
+    for (const auto& location : locations) {
+        ALOGI("elapsedRealtime: flags = %d, timestampNs: %" PRId64 ", timeUncertaintyNs=%lf",
+              location.elapsedRealtime.flags, location.elapsedRealtime.timestampNs,
+              location.elapsedRealtime.timeUncertaintyNs);
+    }
+    batched_locations_cbq_.store(locations);
+    return android::binder::Status::ok();
+}
diff --git a/gnss/aidl/vts/GnssBatchingCallback.h b/gnss/aidl/vts/GnssBatchingCallback.h
new file mode 100644
index 0000000..310a134
--- /dev/null
+++ b/gnss/aidl/vts/GnssBatchingCallback.h
@@ -0,0 +1,36 @@
+/*
+ * 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/hardware/gnss/BnGnssBatchingCallback.h>
+#include <vector>
+#include "GnssCallbackEventQueue.h"
+
+/** Implementation for IGnssBatchingCallback. */
+class GnssBatchingCallback : public android::hardware::gnss::BnGnssBatchingCallback {
+  public:
+    GnssBatchingCallback() : batched_locations_cbq_("batched_locations") {}
+    ~GnssBatchingCallback() {}
+
+    android::binder::Status gnssLocationBatchCb(
+            const std::vector<android::hardware::gnss::GnssLocation>& locations) override;
+
+    android::hardware::gnss::common::GnssCallbackEventQueue<
+            std::vector<android::hardware::gnss::GnssLocation>>
+            batched_locations_cbq_;
+    std::vector<android::hardware::gnss::GnssLocation> last_batched_locations_;
+};
diff --git a/gnss/aidl/vts/gnss_hal_test.h b/gnss/aidl/vts/gnss_hal_test.h
index f72f7fe..e3ecbed 100644
--- a/gnss/aidl/vts/gnss_hal_test.h
+++ b/gnss/aidl/vts/gnss_hal_test.h
@@ -23,6 +23,7 @@
 #include <binder/IServiceManager.h>
 
 #include <android/hardware/gnss/2.1/IGnss.h>
+#include "GnssBatchingCallback.h"
 #include "GnssCallbackAidl.h"
 #include "v2_1/gnss_hal_test_template.h"
 
diff --git a/gnss/aidl/vts/gnss_hal_test_cases.cpp b/gnss/aidl/vts/gnss_hal_test_cases.cpp
index 0cd782e..86140cc 100644
--- a/gnss/aidl/vts/gnss_hal_test_cases.cpp
+++ b/gnss/aidl/vts/gnss_hal_test_cases.cpp
@@ -17,10 +17,12 @@
 #define LOG_TAG "GnssHalTestCases"
 
 #include <android/hardware/gnss/IGnss.h>
+#include <android/hardware/gnss/IGnssBatching.h>
 #include <android/hardware/gnss/IGnssMeasurementCallback.h>
 #include <android/hardware/gnss/IGnssMeasurementInterface.h>
 #include <android/hardware/gnss/IGnssPowerIndication.h>
 #include <android/hardware/gnss/IGnssPsds.h>
+#include "GnssBatchingCallback.h"
 #include "GnssMeasurementCallbackAidl.h"
 #include "GnssPowerIndicationCallback.h"
 #include "gnss_hal_test.h"
@@ -33,6 +35,8 @@
 using android::hardware::gnss::GnssMeasurement;
 using android::hardware::gnss::GnssPowerStats;
 using android::hardware::gnss::IGnss;
+using android::hardware::gnss::IGnssBatching;
+using android::hardware::gnss::IGnssBatchingCallback;
 using android::hardware::gnss::IGnssConfiguration;
 using android::hardware::gnss::IGnssMeasurementCallback;
 using android::hardware::gnss::IGnssMeasurementInterface;
@@ -749,3 +753,25 @@
     status = gnss_configuration_hal->setBlocklist(sources);
     ASSERT_TRUE(status.isOk());
 }
+
+/*
+ * TestGnssBatchingExtension:
+ * 1. Gets the IGnssBatching extension.
+ * 2. Initializes the interface with an IGnssBatchingCallback.
+ * 3. Clean up.
+ */
+TEST_P(GnssHalTest, TestGnssBatchingExtension) {
+    sp<IGnssBatching> iGnssBatching;
+    auto status = aidl_gnss_hal_->getExtensionGnssBatching(&iGnssBatching);
+    if (!status.isOk() || iGnssBatching == nullptr) {
+        // Device doesn't support batching. Skip the test.
+        return;
+    }
+
+    sp<IGnssBatchingCallback> iGnssBatchingCallback;
+    status = iGnssBatching->init(iGnssBatchingCallback);
+    ASSERT_TRUE(status.isOk());
+
+    status = iGnssBatching->cleanup();
+    ASSERT_TRUE(status.isOk());
+}
diff --git a/gnss/common/utils/default/Android.bp b/gnss/common/utils/default/Android.bp
index 5294409..05bec88 100644
--- a/gnss/common/utils/default/Android.bp
+++ b/gnss/common/utils/default/Android.bp
@@ -56,6 +56,6 @@
         "android.hardware.gnss@2.1",
         "android.hardware.gnss.measurement_corrections@1.1",
         "android.hardware.gnss.measurement_corrections@1.0",
-        "android.hardware.gnss-V1-ndk",
+        "android.hardware.gnss-V2-ndk",
     ],
 }
diff --git a/gnss/common/utils/default/Utils.cpp b/gnss/common/utils/default/Utils.cpp
index dfcf9a9..c339e72 100644
--- a/gnss/common/utils/default/Utils.cpp
+++ b/gnss/common/utils/default/Utils.cpp
@@ -28,6 +28,7 @@
 using aidl::android::hardware::gnss::ElapsedRealtime;
 using aidl::android::hardware::gnss::GnssClock;
 using aidl::android::hardware::gnss::GnssData;
+using aidl::android::hardware::gnss::GnssLocation;
 using aidl::android::hardware::gnss::GnssMeasurement;
 using aidl::android::hardware::gnss::IGnss;
 using aidl::android::hardware::gnss::IGnssMeasurementCallback;
@@ -232,6 +233,30 @@
     return gnssData;
 }
 
+GnssLocation Utils::getMockLocation() {
+    ElapsedRealtime elapsedRealtime = {
+            .flags = ElapsedRealtime::HAS_TIMESTAMP_NS | ElapsedRealtime::HAS_TIME_UNCERTAINTY_NS,
+            .timestampNs = ::android::elapsedRealtimeNano(),
+            // This is an hardcoded value indicating a 1ms of uncertainty between the two clocks.
+            // In an actual implementation provide an estimate of the synchronization uncertainty
+            // or don't set the field.
+            .timeUncertaintyNs = 1020400};
+    GnssLocation location = {.gnssLocationFlags = 0xFF,
+                             .latitudeDegrees = gMockLatitudeDegrees,
+                             .longitudeDegrees = gMockLongitudeDegrees,
+                             .altitudeMeters = gMockAltitudeMeters,
+                             .speedMetersPerSec = gMockSpeedMetersPerSec,
+                             .bearingDegrees = gMockBearingDegrees,
+                             .horizontalAccuracyMeters = kMockHorizontalAccuracyMeters,
+                             .verticalAccuracyMeters = kMockVerticalAccuracyMeters,
+                             .speedAccuracyMetersPerSecond = kMockSpeedAccuracyMetersPerSecond,
+                             .bearingAccuracyDegrees = kMockBearingAccuracyDegrees,
+                             .timestampMillis = static_cast<int64_t>(
+                                     kMockTimestamp + ::android::elapsedRealtimeNano() / 1e6),
+                             .elapsedRealtime = elapsedRealtime};
+    return location;
+}
+
 V2_0::GnssLocation Utils::getMockLocationV2_0() {
     const V2_0::ElapsedRealtime timestamp = {
             .flags = V2_0::ElapsedRealtimeFlags::HAS_TIMESTAMP_NS |
diff --git a/gnss/common/utils/default/include/Utils.h b/gnss/common/utils/default/include/Utils.h
index 43772ce..4500ee6 100644
--- a/gnss/common/utils/default/include/Utils.h
+++ b/gnss/common/utils/default/include/Utils.h
@@ -17,6 +17,7 @@
 #ifndef android_hardware_gnss_common_default_Utils_H_
 #define android_hardware_gnss_common_default_Utils_H_
 
+#include <aidl/android/hardware/gnss/BnGnss.h>
 #include <aidl/android/hardware/gnss/BnGnssMeasurementInterface.h>
 #include <android/hardware/gnss/1.0/IGnss.h>
 #include <android/hardware/gnss/2.0/IGnss.h>
@@ -34,6 +35,7 @@
             const bool enableCorrVecOutputs);
     static V2_0::IGnssMeasurementCallback::GnssData getMockMeasurementV2_0();
     static V2_1::IGnssMeasurementCallback::GnssData getMockMeasurementV2_1();
+    static aidl::android::hardware::gnss::GnssLocation getMockLocation();
     static V2_0::GnssLocation getMockLocationV2_0();
     static V1_0::GnssLocation getMockLocationV1_0();
     static hidl_vec<V2_1::IGnssCallback::GnssSvInfo> getMockSvInfoListV2_1();
diff --git a/gnss/common/utils/vts/Utils.cpp b/gnss/common/utils/vts/Utils.cpp
index 9c84e80..be22ff6 100644
--- a/gnss/common/utils/vts/Utils.cpp
+++ b/gnss/common/utils/vts/Utils.cpp
@@ -24,12 +24,10 @@
 namespace gnss {
 namespace common {
 
-using GnssConstellationType_V1_0 = V1_0::GnssConstellationType;
-using GnssConstellationType_V2_0 = V2_0::GnssConstellationType;
-
+using namespace measurement_corrections::V1_0;
 using V1_0::GnssLocationFlags;
 
-void Utils::checkLocation(const GnssLocation& location, bool check_speed,
+void Utils::checkLocation(const V1_0::GnssLocation& location, bool check_speed,
                           bool check_more_accuracies) {
     EXPECT_TRUE(location.gnssLocationFlags & GnssLocationFlags::HAS_LAT_LONG);
     EXPECT_TRUE(location.gnssLocationFlags & GnssLocationFlags::HAS_ALTITUDE);
@@ -96,7 +94,7 @@
     EXPECT_GT(location.timestamp, 1.48e12);
 }
 
-const MeasurementCorrections_1_0 Utils::getMockMeasurementCorrections() {
+const MeasurementCorrections Utils::getMockMeasurementCorrections() {
     ReflectingPlane reflectingPlane = {
             .latitudeDegrees = 37.4220039,
             .longitudeDegrees = -122.0840991,
@@ -104,12 +102,12 @@
             .azimuthDegrees = 203.0,
     };
 
-    SingleSatCorrection_V1_0 singleSatCorrection1 = {
+    SingleSatCorrection singleSatCorrection1 = {
             .singleSatCorrectionFlags = GnssSingleSatCorrectionFlags::HAS_SAT_IS_LOS_PROBABILITY |
                                         GnssSingleSatCorrectionFlags::HAS_EXCESS_PATH_LENGTH |
                                         GnssSingleSatCorrectionFlags::HAS_EXCESS_PATH_LENGTH_UNC |
                                         GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE,
-            .constellation = GnssConstellationType_V1_0::GPS,
+            .constellation = V1_0::GnssConstellationType::GPS,
             .svid = 12,
             .carrierFrequencyHz = 1.59975e+09,
             .probSatIsLos = 0.50001,
@@ -117,11 +115,11 @@
             .excessPathLengthUncertaintyMeters = 25.5,
             .reflectingPlane = reflectingPlane,
     };
-    SingleSatCorrection_V1_0 singleSatCorrection2 = {
+    SingleSatCorrection singleSatCorrection2 = {
             .singleSatCorrectionFlags = GnssSingleSatCorrectionFlags::HAS_SAT_IS_LOS_PROBABILITY |
                                         GnssSingleSatCorrectionFlags::HAS_EXCESS_PATH_LENGTH |
                                         GnssSingleSatCorrectionFlags::HAS_EXCESS_PATH_LENGTH_UNC,
-            .constellation = GnssConstellationType_V1_0::GPS,
+            .constellation = V1_0::GnssConstellationType::GPS,
             .svid = 9,
             .carrierFrequencyHz = 1.59975e+09,
             .probSatIsLos = 0.873,
@@ -129,9 +127,9 @@
             .excessPathLengthUncertaintyMeters = 10.0,
     };
 
-    hidl_vec<SingleSatCorrection_V1_0> singleSatCorrections = {singleSatCorrection1,
-                                                               singleSatCorrection2};
-    MeasurementCorrections_1_0 mockCorrections = {
+    hidl_vec<SingleSatCorrection> singleSatCorrections = {singleSatCorrection1,
+                                                          singleSatCorrection2};
+    MeasurementCorrections mockCorrections = {
             .latitudeDegrees = 37.4219999,
             .longitudeDegrees = -122.0840575,
             .altitudeMeters = 30.60062531,
@@ -143,25 +141,26 @@
     return mockCorrections;
 }
 
-const MeasurementCorrections_1_1 Utils::getMockMeasurementCorrections_1_1() {
-    MeasurementCorrections_1_0 mockCorrections_1_0 = getMockMeasurementCorrections();
+const measurement_corrections::V1_1::MeasurementCorrections
+Utils::getMockMeasurementCorrections_1_1() {
+    MeasurementCorrections mockCorrections_1_0 = getMockMeasurementCorrections();
 
-    SingleSatCorrection_V1_1 singleSatCorrection1 = {
+    measurement_corrections::V1_1::SingleSatCorrection singleSatCorrection1 = {
             .v1_0 = mockCorrections_1_0.satCorrections[0],
-            .constellation = GnssConstellationType_V2_0::IRNSS,
+            .constellation = V2_0::GnssConstellationType::IRNSS,
     };
-    SingleSatCorrection_V1_1 singleSatCorrection2 = {
+    measurement_corrections::V1_1::SingleSatCorrection singleSatCorrection2 = {
             .v1_0 = mockCorrections_1_0.satCorrections[1],
-            .constellation = GnssConstellationType_V2_0::IRNSS,
+            .constellation = V2_0::GnssConstellationType::IRNSS,
     };
 
-    mockCorrections_1_0.satCorrections[0].constellation = GnssConstellationType_V1_0::UNKNOWN;
-    mockCorrections_1_0.satCorrections[1].constellation = GnssConstellationType_V1_0::UNKNOWN;
+    mockCorrections_1_0.satCorrections[0].constellation = V1_0::GnssConstellationType::UNKNOWN;
+    mockCorrections_1_0.satCorrections[1].constellation = V1_0::GnssConstellationType::UNKNOWN;
 
-    hidl_vec<SingleSatCorrection_V1_1> singleSatCorrections = {singleSatCorrection1,
-                                                               singleSatCorrection2};
+    hidl_vec<measurement_corrections::V1_1::SingleSatCorrection> singleSatCorrections = {
+            singleSatCorrection1, singleSatCorrection2};
 
-    MeasurementCorrections_1_1 mockCorrections_1_1 = {
+    measurement_corrections::V1_1::MeasurementCorrections mockCorrections_1_1 = {
             .v1_0 = mockCorrections_1_0,
             .hasEnvironmentBearing = true,
             .environmentBearingDegrees = 45.0,
@@ -177,22 +176,22 @@
  * GnssConstellationType_1_0 type constellation. For constellations that do not have
  * an equivalent value, maps to GnssConstellationType_1_0::UNKNOWN
  */
-GnssConstellationType_1_0 Utils::mapConstellationType(GnssConstellationType_2_0 constellation) {
+V1_0::GnssConstellationType Utils::mapConstellationType(V2_0::GnssConstellationType constellation) {
     switch (constellation) {
-        case GnssConstellationType_2_0::GPS:
-            return GnssConstellationType_1_0::GPS;
-        case GnssConstellationType_2_0::SBAS:
-            return GnssConstellationType_1_0::SBAS;
-        case GnssConstellationType_2_0::GLONASS:
-            return GnssConstellationType_1_0::GLONASS;
-        case GnssConstellationType_2_0::QZSS:
-            return GnssConstellationType_1_0::QZSS;
-        case GnssConstellationType_2_0::BEIDOU:
-            return GnssConstellationType_1_0::BEIDOU;
-        case GnssConstellationType_2_0::GALILEO:
-            return GnssConstellationType_1_0::GALILEO;
+        case V2_0::GnssConstellationType::GPS:
+            return V1_0::GnssConstellationType::GPS;
+        case V2_0::GnssConstellationType::SBAS:
+            return V1_0::GnssConstellationType::SBAS;
+        case V2_0::GnssConstellationType::GLONASS:
+            return V1_0::GnssConstellationType::GLONASS;
+        case V2_0::GnssConstellationType::QZSS:
+            return V1_0::GnssConstellationType::QZSS;
+        case V2_0::GnssConstellationType::BEIDOU:
+            return V1_0::GnssConstellationType::BEIDOU;
+        case V2_0::GnssConstellationType::GALILEO:
+            return V1_0::GnssConstellationType::GALILEO;
         default:
-            return GnssConstellationType_1_0::UNKNOWN;
+            return V1_0::GnssConstellationType::UNKNOWN;
     }
 }
 
diff --git a/gnss/common/utils/vts/include/Utils.h b/gnss/common/utils/vts/include/Utils.h
index a4aad80..91c1167 100644
--- a/gnss/common/utils/vts/include/Utils.h
+++ b/gnss/common/utils/vts/include/Utils.h
@@ -22,33 +22,21 @@
 #include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
 #include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
 
-using GnssConstellationType_1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
-using GnssConstellationType_2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
-using GnssLocation = ::android::hardware::gnss::V1_0::GnssLocation;
-using namespace android::hardware::gnss::measurement_corrections::V1_0;
-
-using MeasurementCorrections_1_0 =
-        android::hardware::gnss::measurement_corrections::V1_0::MeasurementCorrections;
-using MeasurementCorrections_1_1 =
-        android::hardware::gnss::measurement_corrections::V1_1::MeasurementCorrections;
-
-using SingleSatCorrection_V1_0 =
-        android::hardware::gnss::measurement_corrections::V1_0::SingleSatCorrection;
-using SingleSatCorrection_V1_1 =
-        android::hardware::gnss::measurement_corrections::V1_1::SingleSatCorrection;
-
 namespace android {
 namespace hardware {
 namespace gnss {
 namespace common {
 
 struct Utils {
-    static void checkLocation(const GnssLocation& location, bool check_speed,
+    static void checkLocation(const V1_0::GnssLocation& location, bool check_speed,
                               bool check_more_accuracies);
-    static const MeasurementCorrections_1_0 getMockMeasurementCorrections();
-    static const MeasurementCorrections_1_1 getMockMeasurementCorrections_1_1();
+    static const measurement_corrections::V1_0::MeasurementCorrections
+    getMockMeasurementCorrections();
+    static const measurement_corrections::V1_1::MeasurementCorrections
+    getMockMeasurementCorrections_1_1();
 
-    static GnssConstellationType_1_0 mapConstellationType(GnssConstellationType_2_0 constellation);
+    static V1_0::GnssConstellationType mapConstellationType(
+            V2_0::GnssConstellationType constellation);
 
     static bool isAutomotiveDevice();
 };
diff --git a/graphics/composer/aidl/aidl_api/android.hardware.graphics.composer3/current/android/hardware/graphics/composer3/IComposerClient.aidl b/graphics/composer/aidl/aidl_api/android.hardware.graphics.composer3/current/android/hardware/graphics/composer3/IComposerClient.aidl
index 5c37018..d7cab2b 100644
--- a/graphics/composer/aidl/aidl_api/android.hardware.graphics.composer3/current/android/hardware/graphics/composer3/IComposerClient.aidl
+++ b/graphics/composer/aidl/aidl_api/android.hardware.graphics.composer3/current/android/hardware/graphics/composer3/IComposerClient.aidl
@@ -85,4 +85,5 @@
   const int EX_UNSUPPORTED = 8;
   const int EX_SEAMLESS_NOT_ALLOWED = 9;
   const int EX_SEAMLESS_NOT_POSSIBLE = 10;
+  const int INVALID_CONFIGURATION = 2147483647;
 }
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerClient.aidl b/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerClient.aidl
index 2a9545d..230980d 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerClient.aidl
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerClient.aidl
@@ -90,6 +90,12 @@
     const int EX_SEAMLESS_NOT_POSSIBLE = 10;
 
     /**
+     * Integer.MAX_VALUE is reserved for the invalid configuration.
+     * This should not be returned as a valid configuration.
+     */
+    const int INVALID_CONFIGURATION = 0x7fffffff;
+
+    /**
      * Creates a new layer on the given display.
      *
      * @param display is the display on which to create the layer.
@@ -282,6 +288,7 @@
     /**
      * Returns handles for all of the valid display configurations on this
      * display.
+     * This should never return INVALID_CONFIGURATION as a valid value.
      *
      * @param display is the display to query.
      *
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
index 8a36b1d..420b32c 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
@@ -29,7 +29,10 @@
         "VtsHalTargetTestDefaults",
         "use_libaidlvintf_gtest_helper_static",
     ],
-    srcs: ["VtsHalGraphicsComposer3_TargetTest.cpp"],
+    srcs: [
+        "VtsHalGraphicsComposer3_TargetTest.cpp",
+        "composer-vts/GraphicsComposerCallback.cpp",
+    ],
 
     // TODO(b/64437680): Assume these libs are always available on the device.
     shared_libs: [
@@ -42,6 +45,7 @@
         "android.hardware.graphics.common@1.2",
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
+        "android.hardware.graphics.composer@3-vts",
     ],
 
     test_suites: [
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_TargetTest.cpp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_TargetTest.cpp
index 40dfe06..97684ca 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_TargetTest.cpp
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_TargetTest.cpp
@@ -6,8 +6,16 @@
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <binder/ProcessState.h>
+#include <composer-vts/include/GraphicsComposerCallback.h>
 #include <gtest/gtest.h>
+#include <algorithm>
+#include <numeric>
+#include <regex>
 #include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
 
 #pragma push_macro("LOG_TAG")
 #undef LOG_TAG
@@ -16,12 +24,14 @@
 namespace aidl::android::hardware::graphics::composer3::vts {
 namespace {
 
+using namespace std::chrono_literals;
+
 class VtsDisplay {
   public:
-    VtsDisplay(uint64_t displayId, int32_t displayWidth, int32_t displayHeight)
+    VtsDisplay(int64_t displayId, int32_t displayWidth, int32_t displayHeight)
         : mDisplayId(displayId), mDisplayWidth(displayWidth), mDisplayHeight(displayHeight) {}
 
-    uint64_t get() const { return mDisplayId; }
+    int64_t get() const { return mDisplayId; }
 
     void setDimensions(int32_t displayWidth, int32_t displayHeight) {
         mDisplayWidth = displayWidth;
@@ -29,7 +39,7 @@
     }
 
   private:
-    const uint64_t mDisplayId;
+    const int64_t mDisplayId;
     int32_t mDisplayWidth;
     int32_t mDisplayHeight;
 };
@@ -44,13 +54,19 @@
         ASSERT_NE(mComposer, nullptr);
         ASSERT_NO_FATAL_FAILURE(mComposer->createClient(&mComposerClient));
         mInvalidDisplayId = GetInvalidDisplayId();
+
+        mComposerCallback = ::ndk::SharedRefBase::make<GraphicsComposerCallback>();
+        EXPECT_TRUE(mComposerClient->registerCallback(mComposerCallback).isOk());
+
+        // assume the first displays are built-in and are never removed
+        mDisplays = waitForDisplays();
     }
 
     // returns an invalid display id (one that has not been registered to a
     // display.  Currently assuming that a device will never have close to
     // std::numeric_limit<uint64_t>::max() displays registered while running tests
-    uint64_t GetInvalidDisplayId() {
-        uint64_t id = std::numeric_limits<uint64_t>::max();
+    int64_t GetInvalidDisplayId() {
+        int64_t id = std::numeric_limits<int64_t>::max();
         while (id > 0) {
             if (std::none_of(mDisplays.begin(), mDisplays.end(),
                              [&](const VtsDisplay& display) { return id == display.get(); })) {
@@ -62,11 +78,99 @@
         return 0;
     }
 
+    std::vector<VtsDisplay> waitForDisplays() {
+        while (true) {
+            // Sleep for a small period of time to allow all built-in displays
+            // to post hotplug events
+            std::this_thread::sleep_for(5ms);
+            std::vector<int64_t> displays = mComposerCallback->getDisplays();
+            if (displays.empty()) {
+                continue;
+            }
+
+            std::vector<VtsDisplay> vtsDisplays;
+            vtsDisplays.reserve(displays.size());
+            for (int64_t display : displays) {
+                int32_t activeConfig;
+                EXPECT_TRUE(mComposerClient->getActiveConfig(display, &activeConfig).isOk());
+                int32_t displayWidth;
+                EXPECT_TRUE(mComposerClient
+                                    ->getDisplayAttribute(display, activeConfig,
+                                                          DisplayAttribute::WIDTH, &displayWidth)
+                                    .isOk());
+                int32_t displayHeight;
+                EXPECT_TRUE(mComposerClient
+                                    ->getDisplayAttribute(display, activeConfig,
+                                                          DisplayAttribute::HEIGHT, &displayHeight)
+                                    .isOk());
+                vtsDisplays.emplace_back(VtsDisplay{display, displayWidth, displayHeight});
+            }
+
+            return vtsDisplays;
+        }
+    }
+
+    // returns an invalid config id which is std::numeric_limit<int32_t>::max()
+    int32_t GetInvalidConfigId() { return IComposerClient::INVALID_CONFIGURATION; }
+
+    ndk::ScopedAStatus setActiveConfigWithConstraints(
+            VtsDisplay& display, int32_t config, const VsyncPeriodChangeConstraints& constraints,
+            VsyncPeriodChangeTimeline* timeline) {
+        auto error = mComposerClient->setActiveConfigWithConstraints(display.get(), config,
+                                                                     constraints, timeline);
+        if (error.isOk()) {
+            int32_t displayWidth;
+            EXPECT_TRUE(mComposerClient
+                                ->getDisplayAttribute(display.get(), config,
+                                                      DisplayAttribute::WIDTH, &displayWidth)
+                                .isOk());
+            int32_t displayHeight;
+            EXPECT_TRUE(mComposerClient
+                                ->getDisplayAttribute(display.get(), config,
+                                                      DisplayAttribute::HEIGHT, &displayHeight)
+                                .isOk());
+            display.setDimensions(displayWidth, displayHeight);
+        }
+        return error;
+    }
+
+    void Test_setContentTypeForDisplay(const int64_t& display,
+                                       const std::vector<ContentType>& capabilities,
+                                       const ContentType& contentType, const char* contentTypeStr) {
+        const bool contentTypeSupport = std::find(capabilities.begin(), capabilities.end(),
+                                                  contentType) != capabilities.end();
+
+        if (!contentTypeSupport) {
+            EXPECT_EQ(IComposerClient::EX_UNSUPPORTED,
+                      mComposerClient->setContentType(display, contentType)
+                              .getServiceSpecificError());
+            GTEST_SUCCEED() << contentTypeStr << " content type is not supported on display "
+                            << std::to_string(display) << ", skipping test";
+            return;
+        }
+
+        EXPECT_TRUE(mComposerClient->setContentType(display, contentType).isOk());
+        EXPECT_TRUE(mComposerClient->setContentType(display, ContentType::NONE).isOk());
+    }
+
+    void Test_setContentType(const ContentType& contentType, const char* contentTypeStr) {
+        for (const auto& display : mDisplays) {
+            std::vector<ContentType> supportedContentTypes;
+            const auto error = mComposerClient->getSupportedContentTypes(display.get(),
+                                                                         &supportedContentTypes);
+            EXPECT_TRUE(error.isOk());
+
+            Test_setContentTypeForDisplay(display.get(), supportedContentTypes, contentType,
+                                          contentTypeStr);
+        }
+    }
+
     std::shared_ptr<IComposer> mComposer;
     std::shared_ptr<IComposerClient> mComposerClient;
-    uint64_t mInvalidDisplayId;
-    std::vector<VtsDisplay>
-            mDisplays;  // TODO(b/202401906) populate all the displays available for test.
+    int64_t mInvalidDisplayId;
+    int64_t mPrimaryDisplay;
+    std::vector<VtsDisplay> mDisplays;
+    std::shared_ptr<GraphicsComposerCallback> mComposerCallback;
 };
 
 TEST_P(GraphicsComposerAidlTest, getDisplayCapabilitiesBadDisplay) {
@@ -85,6 +189,557 @@
     }
 }
 
+TEST_P(GraphicsComposerAidlTest, GetDisplayIdentificationData) {
+    DisplayIdentification displayIdentification0;
+
+    const auto error =
+            mComposerClient->getDisplayIdentificationData(mPrimaryDisplay, &displayIdentification0);
+    if (error.getServiceSpecificError() == IComposerClient::EX_UNSUPPORTED) {
+        return;
+    }
+    ASSERT_TRUE(error.isOk()) << "failed to get display identification data";
+    ASSERT_FALSE(displayIdentification0.data.empty());
+
+    constexpr size_t kEdidBlockSize = 128;
+    ASSERT_TRUE(displayIdentification0.data.size() % kEdidBlockSize == 0)
+            << "EDID blob length is not a multiple of " << kEdidBlockSize;
+
+    const uint8_t kEdidHeader[] = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
+    ASSERT_TRUE(std::equal(std::begin(kEdidHeader), std::end(kEdidHeader),
+                           displayIdentification0.data.begin()))
+            << "EDID blob doesn't start with the fixed EDID header";
+    ASSERT_EQ(0, std::accumulate(displayIdentification0.data.begin(),
+                                 displayIdentification0.data.begin() + kEdidBlockSize,
+                                 static_cast<uint8_t>(0)))
+            << "EDID base block doesn't checksum";
+
+    DisplayIdentification displayIdentification1;
+    ASSERT_TRUE(
+            mComposerClient->getDisplayIdentificationData(mPrimaryDisplay, &displayIdentification1)
+                    .isOk());
+
+    ASSERT_EQ(displayIdentification0.port, displayIdentification1.port) << "ports are not stable";
+    ASSERT_TRUE(displayIdentification0.data.size() == displayIdentification1.data.size() &&
+                std::equal(displayIdentification0.data.begin(), displayIdentification0.data.end(),
+                           displayIdentification1.data.begin()))
+            << "data is not stable";
+}
+
+TEST_P(GraphicsComposerAidlTest, GetHdrCapabilities) {
+    HdrCapabilities hdrCapabilities;
+    const auto error = mComposerClient->getHdrCapabilities(mPrimaryDisplay, &hdrCapabilities);
+
+    ASSERT_TRUE(error.isOk());
+    ASSERT_TRUE(hdrCapabilities.maxLuminance >= hdrCapabilities.minLuminance);
+}
+
+TEST_P(GraphicsComposerAidlTest, GetPerFrameMetadataKeys) {
+    std::vector<PerFrameMetadataKey> keys;
+    const auto error = mComposerClient->getPerFrameMetadataKeys(mPrimaryDisplay, &keys);
+
+    if (error.isOk()) {
+        EXPECT_EQ(EX_NONE, error.getServiceSpecificError());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, GetReadbackBufferAttributes) {
+    ReadbackBufferAttributes readBackBufferAttributes;
+    const auto error = mComposerClient->getReadbackBufferAttributes(mPrimaryDisplay,
+                                                                    &readBackBufferAttributes);
+
+    if (error.isOk()) {
+        EXPECT_EQ(EX_NONE, error.getServiceSpecificError());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, GetRenderIntents) {
+    std::vector<ColorMode> modes;
+    EXPECT_TRUE(mComposerClient->getColorModes(mPrimaryDisplay, &modes).isOk());
+    for (auto mode : modes) {
+        std::vector<RenderIntent> intents;
+        EXPECT_TRUE(mComposerClient->getRenderIntents(mPrimaryDisplay, mode, &intents).isOk());
+
+        bool isHdr;
+        switch (mode) {
+            case ColorMode::BT2100_PQ:
+            case ColorMode::BT2100_HLG:
+                isHdr = true;
+                break;
+            default:
+                isHdr = false;
+                break;
+        }
+        RenderIntent requiredIntent =
+                isHdr ? RenderIntent::TONE_MAP_COLORIMETRIC : RenderIntent::COLORIMETRIC;
+
+        auto iter = std::find(intents.cbegin(), intents.cend(), requiredIntent);
+        EXPECT_NE(intents.cend(), iter);
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, GetRenderIntentsBadDisplay) {
+    std::vector<ColorMode> modes;
+    EXPECT_TRUE(mComposerClient->getColorModes(mPrimaryDisplay, &modes).isOk());
+    for (auto mode : modes) {
+        std::vector<RenderIntent> renderIntents;
+        const auto error =
+                mComposerClient->getRenderIntents(mInvalidDisplayId, mode, &renderIntents);
+        EXPECT_FALSE(error.isOk());
+        EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, GetRenderIntentsBadParameter) {
+    std::vector<RenderIntent> renderIntents;
+    const auto error = mComposerClient->getRenderIntents(
+            mPrimaryDisplay, static_cast<ColorMode>(-1), &renderIntents);
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_PARAMETER, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, GetColorModes) {
+    std::vector<ColorMode> colorModes;
+    EXPECT_TRUE(mComposerClient->getColorModes(mPrimaryDisplay, &colorModes).isOk());
+
+    auto native = std::find(colorModes.cbegin(), colorModes.cend(), ColorMode::NATIVE);
+    ASSERT_NE(colorModes.cend(), native);
+}
+
+TEST_P(GraphicsComposerAidlTest, GetColorModeBadDisplay) {
+    std::vector<ColorMode> colorModes;
+    const auto error = mComposerClient->getColorModes(mInvalidDisplayId, &colorModes);
+
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, SetColorMode) {
+    std::vector<ColorMode> colorModes;
+    EXPECT_TRUE(mComposerClient->getColorModes(mPrimaryDisplay, &colorModes).isOk());
+    for (auto mode : colorModes) {
+        std::vector<RenderIntent> intents;
+        EXPECT_TRUE(mComposerClient->getRenderIntents(mPrimaryDisplay, mode, &intents).isOk())
+                << "failed to get render intents";
+        for (auto intent : intents) {
+            const auto error = mComposerClient->setColorMode(mPrimaryDisplay, mode, intent);
+            EXPECT_TRUE(error.isOk() ||
+                        IComposerClient::EX_UNSUPPORTED == error.getServiceSpecificError())
+                    << "failed to set color mode";
+        }
+    }
+
+    const auto error = mComposerClient->setColorMode(mPrimaryDisplay, ColorMode::NATIVE,
+                                                     RenderIntent::COLORIMETRIC);
+    EXPECT_TRUE(error.isOk() || IComposerClient::EX_UNSUPPORTED == error.getServiceSpecificError())
+            << "failed to set color mode";
+}
+
+TEST_P(GraphicsComposerAidlTest, SetColorModeBadDisplay) {
+    auto const error = mComposerClient->setColorMode(mInvalidDisplayId, ColorMode::NATIVE,
+                                                     RenderIntent::COLORIMETRIC);
+
+    EXPECT_FALSE(error.isOk());
+    ASSERT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, SetColorModeBadParameter) {
+    const auto colorModeError = mComposerClient->setColorMode(
+            mPrimaryDisplay, static_cast<ColorMode>(-1), RenderIntent::COLORIMETRIC);
+
+    EXPECT_FALSE(colorModeError.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_PARAMETER, colorModeError.getServiceSpecificError());
+
+    const auto renderIntentError = mComposerClient->setColorMode(mPrimaryDisplay, ColorMode::NATIVE,
+                                                                 static_cast<RenderIntent>(-1));
+
+    EXPECT_FALSE(renderIntentError.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_PARAMETER, renderIntentError.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, GetDisplayedContentSamplingAttributes) {
+    int constexpr invalid = -1;
+
+    DisplayContentSamplingAttributes format;
+    auto error = mComposerClient->getDisplayedContentSamplingAttributes(mPrimaryDisplay, &format);
+
+    if (error.getServiceSpecificError() == IComposerClient::EX_UNSUPPORTED) {
+        SUCCEED() << "Device does not support optional extension. Test skipped";
+        return;
+    }
+
+    EXPECT_TRUE(error.isOk());
+    EXPECT_NE(format.format, static_cast<common::PixelFormat>(invalid));
+    EXPECT_NE(format.dataspace, static_cast<common::Dataspace>(invalid));
+    EXPECT_NE(format.componentMask, static_cast<FormatColorComponent>(invalid));
+};
+
+TEST_P(GraphicsComposerAidlTest, SetDisplayedContentSamplingEnabled) {
+    auto const maxFrames = 10;
+    FormatColorComponent enableAllComponents = FormatColorComponent::FORMAT_COMPONENT_0;
+    auto error = mComposerClient->setDisplayedContentSamplingEnabled(
+            mPrimaryDisplay, true, enableAllComponents, maxFrames);
+    if (error.getServiceSpecificError() == IComposerClient::EX_UNSUPPORTED) {
+        SUCCEED() << "Device does not support optional extension. Test skipped";
+        return;
+    }
+    EXPECT_TRUE(error.isOk());
+
+    error = mComposerClient->setDisplayedContentSamplingEnabled(mPrimaryDisplay, false,
+                                                                enableAllComponents, maxFrames);
+    EXPECT_TRUE(error.isOk());
+}
+
+TEST_P(GraphicsComposerAidlTest, GetDisplayedContentSample) {
+    DisplayContentSamplingAttributes displayContentSamplingAttributes;
+    int constexpr invalid = -1;
+    displayContentSamplingAttributes.format = static_cast<common::PixelFormat>(invalid);
+    displayContentSamplingAttributes.dataspace = static_cast<common::Dataspace>(invalid);
+    displayContentSamplingAttributes.componentMask = static_cast<FormatColorComponent>(invalid);
+    EXPECT_TRUE(mComposerClient
+                        ->getDisplayedContentSamplingAttributes(mPrimaryDisplay,
+                                                                &displayContentSamplingAttributes)
+                        .isOk());
+
+    uint64_t maxFrames = 10;
+    uint64_t timestamp = 0;
+    uint64_t frameCount = 0;
+    DisplayContentSample displayContentSample;
+    auto error = mComposerClient->getDisplayedContentSample(mPrimaryDisplay, maxFrames, timestamp,
+                                                            &displayContentSample);
+    if (error.getServiceSpecificError() == IComposerClient::EX_UNSUPPORTED) {
+        SUCCEED() << "Device does not support optional extension. Test skipped";
+        return;
+    }
+
+    EXPECT_TRUE(error.isOk());
+    EXPECT_LE(frameCount, maxFrames);
+    std::vector<std::vector<int64_t>> histogram = {
+            displayContentSample.sampleComponent0, displayContentSample.sampleComponent1,
+            displayContentSample.sampleComponent2, displayContentSample.sampleComponent3};
+
+    for (auto i = 0; i < histogram.size(); i++) {
+        if (static_cast<int>(displayContentSamplingAttributes.componentMask) & (1 << i)) {
+            EXPECT_NE(histogram[i].size(), 0);
+        } else {
+            EXPECT_EQ(histogram[i].size(), 0);
+        }
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, getDisplayCapabilitiesBasic) {
+    std::vector<DisplayCapability> capabilities;
+    const auto error = mComposerClient->getDisplayCapabilities(mPrimaryDisplay, &capabilities);
+    ASSERT_TRUE(error.isOk());
+    const bool hasDozeSupport = std::find(capabilities.begin(), capabilities.end(),
+                                          DisplayCapability::DOZE) != capabilities.end();
+    bool isDozeSupported = false;
+    EXPECT_TRUE(mComposerClient->getDozeSupport(mPrimaryDisplay, &isDozeSupported).isOk());
+    EXPECT_EQ(hasDozeSupport, isDozeSupported);
+
+    bool hasBrightnessSupport = std::find(capabilities.begin(), capabilities.end(),
+                                          DisplayCapability::BRIGHTNESS) != capabilities.end();
+    bool isBrightnessSupported = false;
+    EXPECT_TRUE(
+            mComposerClient->getDisplayBrightnessSupport(mPrimaryDisplay, &isBrightnessSupported)
+                    .isOk());
+    EXPECT_EQ(isBrightnessSupported, hasBrightnessSupport);
+}
+
+/*
+ * Test that if brightness operations are supported, setDisplayBrightness works as expected.
+ */
+TEST_P(GraphicsComposerAidlTest, setDisplayBrightness) {
+    std::vector<DisplayCapability> capabilities;
+    auto error = mComposerClient->getDisplayCapabilities(mPrimaryDisplay, &capabilities);
+    ASSERT_TRUE(error.isOk());
+    bool brightnessSupport = std::find(capabilities.begin(), capabilities.end(),
+                                       DisplayCapability::BRIGHTNESS) != capabilities.end();
+    if (!brightnessSupport) {
+        EXPECT_EQ(mComposerClient->setDisplayBrightness(mPrimaryDisplay, 0.5f)
+                          .getServiceSpecificError(),
+                  IComposerClient::EX_UNSUPPORTED);
+        GTEST_SUCCEED() << "Brightness operations are not supported";
+        return;
+    }
+
+    EXPECT_TRUE(mComposerClient->setDisplayBrightness(mPrimaryDisplay, 0.0f).isOk());
+    EXPECT_TRUE(mComposerClient->setDisplayBrightness(mPrimaryDisplay, 0.5f).isOk());
+    EXPECT_TRUE(mComposerClient->setDisplayBrightness(mPrimaryDisplay, 1.0f).isOk());
+    EXPECT_TRUE(mComposerClient->setDisplayBrightness(mPrimaryDisplay, -1.0f).isOk());
+
+    error = mComposerClient->setDisplayBrightness(mPrimaryDisplay, +2.0f);
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(error.getServiceSpecificError(), IComposerClient::EX_BAD_PARAMETER);
+
+    error = mComposerClient->setDisplayBrightness(mPrimaryDisplay, -2.0f);
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(error.getServiceSpecificError(), IComposerClient::EX_BAD_PARAMETER);
+}
+
+TEST_P(GraphicsComposerAidlTest, getDisplayConnectionType) {
+    DisplayConnectionType type;
+    EXPECT_FALSE(mComposerClient->getDisplayConnectionType(mInvalidDisplayId, &type).isOk());
+    for (const auto& display : mDisplays) {
+        EXPECT_TRUE(mComposerClient->getDisplayConnectionType(display.get(), &type).isOk());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, getDisplayAttribute) {
+    for (const auto& display : mDisplays) {
+        std::vector<int32_t> configs;
+        mComposerClient->getDisplayConfigs(display.get(), &configs);
+        for (const auto& config : configs) {
+            const std::array<DisplayAttribute, 4> requiredAttributes = {{
+                    DisplayAttribute::WIDTH,
+                    DisplayAttribute::HEIGHT,
+                    DisplayAttribute::VSYNC_PERIOD,
+                    DisplayAttribute::CONFIG_GROUP,
+            }};
+            int32_t value;
+            for (const auto& attribute : requiredAttributes) {
+                EXPECT_TRUE(mComposerClient
+                                    ->getDisplayAttribute(display.get(), config, attribute, &value)
+                                    .isOk());
+                EXPECT_NE(-1, value);
+            }
+
+            const std::array<DisplayAttribute, 2> optionalAttributes = {{
+                    DisplayAttribute::DPI_X,
+                    DisplayAttribute::DPI_Y,
+            }};
+            for (const auto& attribute : optionalAttributes) {
+                const auto error = mComposerClient->getDisplayAttribute(display.get(), config,
+                                                                        attribute, &value);
+                if (error.isOk()) {
+                    EXPECT_EQ(EX_NONE, error.getServiceSpecificError());
+                } else {
+                    EXPECT_EQ(IComposerClient::EX_UNSUPPORTED, error.getServiceSpecificError());
+                }
+            }
+        }
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, checkConfigsAreValid) {
+    for (const auto& display : mDisplays) {
+        std::vector<int32_t> configs;
+        mComposerClient->getDisplayConfigs(display.get(), &configs);
+
+        EXPECT_FALSE(std::any_of(configs.begin(), configs.end(), [](auto config) {
+            return config == IComposerClient::INVALID_CONFIGURATION;
+        }));
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, getDisplayAttributeConfigsInAGroupDifferOnlyByVsyncPeriod) {
+    struct Resolution {
+        int32_t width;
+        int32_t height;
+    };
+    struct Dpi {
+        int32_t x;
+        int32_t y;
+    };
+    for (const auto& display : mDisplays) {
+        std::vector<int32_t> configs;
+        EXPECT_TRUE(mComposerClient->getDisplayConfigs(display.get(), &configs).isOk());
+        std::unordered_map<int32_t, Resolution> configGroupToResolutionMap;
+        std::unordered_map<int32_t, Dpi> configGroupToDpiMap;
+        for (const auto& config : configs) {
+            int32_t configGroup = -1;
+            EXPECT_TRUE(mComposerClient
+                                ->getDisplayAttribute(display.get(), config,
+                                                      DisplayAttribute::CONFIG_GROUP, &configGroup)
+                                .isOk());
+            int32_t width = -1;
+            EXPECT_TRUE(mComposerClient
+                                ->getDisplayAttribute(display.get(), config,
+                                                      DisplayAttribute::WIDTH, &width)
+                                .isOk());
+            int32_t height = -1;
+            EXPECT_TRUE(mComposerClient
+                                ->getDisplayAttribute(display.get(), config,
+                                                      DisplayAttribute::HEIGHT, &height)
+                                .isOk());
+            if (configGroupToResolutionMap.find(configGroup) == configGroupToResolutionMap.end()) {
+                configGroupToResolutionMap[configGroup] = {width, height};
+            }
+            EXPECT_EQ(configGroupToResolutionMap[configGroup].width, width);
+            EXPECT_EQ(configGroupToResolutionMap[configGroup].height, height);
+
+            int32_t dpiX = -1;
+            mComposerClient->getDisplayAttribute(display.get(), config, DisplayAttribute::DPI_X,
+                                                 &dpiX);
+
+            int32_t dpiY = -1;
+            mComposerClient->getDisplayAttribute(display.get(), config, DisplayAttribute::DPI_Y,
+                                                 &dpiY);
+            if (dpiX == -1 && dpiY == -1) {
+                continue;
+            }
+
+            if (configGroupToDpiMap.find(configGroup) == configGroupToDpiMap.end()) {
+                configGroupToDpiMap[configGroup] = {dpiX, dpiY};
+            }
+            EXPECT_EQ(configGroupToDpiMap[configGroup].x, dpiX);
+            EXPECT_EQ(configGroupToDpiMap[configGroup].y, dpiY);
+        }
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, getDisplayVsyncPeriod_BadDisplay) {
+    int32_t vsyncPeriodNanos;
+    const auto error = mComposerClient->getDisplayVsyncPeriod(mInvalidDisplayId, &vsyncPeriodNanos);
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, setActiveConfigWithConstraints_BadDisplay) {
+    VsyncPeriodChangeTimeline timeline;
+    VsyncPeriodChangeConstraints constraints;
+
+    constraints.seamlessRequired = false;
+    constraints.desiredTimeNanos = systemTime();
+    int32_t config = 0;
+    auto const error = mComposerClient->setActiveConfigWithConstraints(mInvalidDisplayId, config,
+                                                                       constraints, &timeline);
+
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, setActiveConfigWithConstraints_BadConfig) {
+    VsyncPeriodChangeTimeline timeline;
+    VsyncPeriodChangeConstraints constraints;
+
+    constraints.seamlessRequired = false;
+    constraints.desiredTimeNanos = systemTime();
+
+    for (VtsDisplay& display : mDisplays) {
+        int32_t invalidConfigId = GetInvalidConfigId();
+        const auto error =
+                setActiveConfigWithConstraints(display, invalidConfigId, constraints, &timeline);
+        EXPECT_FALSE(error.isOk());
+        EXPECT_EQ(IComposerClient::EX_BAD_CONFIG, error.getServiceSpecificError());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, setAutoLowLatencyModeBadDisplay) {
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY,
+              mComposerClient->setAutoLowLatencyMode(mInvalidDisplayId, true)
+                      .getServiceSpecificError());
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY,
+              mComposerClient->setAutoLowLatencyMode(mInvalidDisplayId, false)
+                      .getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, setAutoLowLatencyMode) {
+    for (const auto& display : mDisplays) {
+        std::vector<DisplayCapability> capabilities;
+        const auto error = mComposerClient->getDisplayCapabilities(display.get(), &capabilities);
+        EXPECT_TRUE(error.isOk());
+
+        const bool allmSupport =
+                std::find(capabilities.begin(), capabilities.end(),
+                          DisplayCapability::AUTO_LOW_LATENCY_MODE) != capabilities.end();
+
+        if (!allmSupport) {
+            const auto errorIsOn = mComposerClient->setAutoLowLatencyMode(display.get(), true);
+            EXPECT_FALSE(errorIsOn.isOk());
+            EXPECT_EQ(IComposerClient::EX_UNSUPPORTED, errorIsOn.getServiceSpecificError());
+            const auto errorIsOff = mComposerClient->setAutoLowLatencyMode(display.get(), false);
+            EXPECT_FALSE(errorIsOff.isOk());
+            EXPECT_EQ(IComposerClient::EX_UNSUPPORTED, errorIsOff.getServiceSpecificError());
+            GTEST_SUCCEED() << "Auto Low Latency Mode is not supported on display "
+                            << std::to_string(display.get()) << ", skipping test";
+            return;
+        }
+
+        EXPECT_TRUE(mComposerClient->setAutoLowLatencyMode(display.get(), true).isOk());
+        EXPECT_TRUE(mComposerClient->setAutoLowLatencyMode(display.get(), false).isOk());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, getSupportedContentTypesBadDisplay) {
+    std::vector<ContentType> supportedContentTypes;
+    const auto error =
+            mComposerClient->getSupportedContentTypes(mInvalidDisplayId, &supportedContentTypes);
+    EXPECT_FALSE(error.isOk());
+    EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+}
+
+TEST_P(GraphicsComposerAidlTest, getSupportedContentTypes) {
+    std::vector<ContentType> supportedContentTypes;
+    for (const auto& display : mDisplays) {
+        supportedContentTypes.clear();
+        const auto error =
+                mComposerClient->getSupportedContentTypes(display.get(), &supportedContentTypes);
+
+        ASSERT_TRUE(error.isOk());
+
+        const bool noneSupported =
+                std::find(supportedContentTypes.begin(), supportedContentTypes.end(),
+                          ContentType::NONE) != supportedContentTypes.end();
+        EXPECT_FALSE(noneSupported);
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, setContentTypeNoneAlwaysAccepted) {
+    for (const auto& display : mDisplays) {
+        const auto error = mComposerClient->setContentType(display.get(), ContentType::NONE);
+        EXPECT_TRUE(error.isOk());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, setContentTypeBadDisplay) {
+    constexpr ContentType types[] = {ContentType::NONE, ContentType::GRAPHICS, ContentType::PHOTO,
+                                     ContentType::CINEMA, ContentType::GAME};
+    for (const auto& type : types) {
+        auto const error = mComposerClient->setContentType(mInvalidDisplayId, type);
+
+        EXPECT_FALSE(error.isOk());
+        EXPECT_EQ(IComposerClient::EX_BAD_DISPLAY, error.getServiceSpecificError());
+    }
+}
+
+TEST_P(GraphicsComposerAidlTest, setGraphicsContentType) {
+    Test_setContentType(ContentType::GRAPHICS, "GRAPHICS");
+}
+
+TEST_P(GraphicsComposerAidlTest, setPhotoContentType) {
+    Test_setContentType(ContentType::PHOTO, "PHOTO");
+}
+
+TEST_P(GraphicsComposerAidlTest, setCinemaContentType) {
+    Test_setContentType(ContentType::CINEMA, "CINEMA");
+}
+
+TEST_P(GraphicsComposerAidlTest, setGameContentType) {
+    Test_setContentType(ContentType::GAME, "GAME");
+}
+
+TEST_P(GraphicsComposerAidlTest, getLayerGenericMetadataKeys) {
+    std::vector<LayerGenericMetadataKey> keys;
+    EXPECT_TRUE(mComposerClient->getLayerGenericMetadataKeys(&keys).isOk());
+
+    std::regex reverseDomainName("^[a-zA-Z-]{2,}(\\.[a-zA-Z0-9-]+)+$");
+    std::unordered_set<std::string> uniqueNames;
+    for (const auto& key : keys) {
+        std::string name(key.name.c_str());
+
+        // Keys must not start with 'android' or 'com.android'
+        EXPECT_FALSE(name.find("android") == 0);
+        EXPECT_FALSE(name.find("com.android") == 0);
+
+        // Keys must be in reverse domain name format
+        EXPECT_TRUE(std::regex_match(name, reverseDomainName));
+
+        // Keys must be unique within this list
+        const auto& [iter, inserted] = uniqueNames.insert(name);
+        EXPECT_TRUE(inserted);
+    }
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GraphicsComposerAidlTest);
 INSTANTIATE_TEST_SUITE_P(
         PerInstance, GraphicsComposerAidlTest,
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/Android.bp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/Android.bp
new file mode 100644
index 0000000..bb5f3f1
--- /dev/null
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/Android.bp
@@ -0,0 +1,44 @@
+/**
+ * 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.
+ */
+
+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: "android.hardware.graphics.composer@3-vts",
+    defaults: ["hidl_defaults"],
+    srcs: [
+        "GraphicsComposerCallback.cpp",
+    ],
+    static_libs: [
+        "android.hardware.graphics.composer3-V1-ndk",
+        "android.hardware.graphics.common-V3-ndk",
+        "libgtest",
+        "libbase",
+    ],
+    cflags: [
+        "-O0",
+        "-g",
+        "-DLOG_TAG=\"ComposerVts\"",
+    ],
+    export_include_dirs: ["include"],
+}
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/GraphicsComposerCallback.cpp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/GraphicsComposerCallback.cpp
new file mode 100644
index 0000000..daf9924
--- /dev/null
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/GraphicsComposerCallback.cpp
@@ -0,0 +1,136 @@
+/**
+ * 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 "include/GraphicsComposerCallback.h"
+#include <log/log_main.h>
+
+#pragma push_macro("LOG_TAG")
+#undef LOG_TAG
+#define LOG_TAG "GraphicsComposerCallback"
+
+namespace aidl::android::hardware::graphics::composer3::vts {
+
+void GraphicsComposerCallback::setVsyncAllowed(bool allowed) {
+    std::scoped_lock lock(mMutex);
+    mVsyncAllowed = allowed;
+}
+
+std::vector<int64_t> GraphicsComposerCallback::getDisplays() const {
+    std::scoped_lock lock(mMutex);
+    return std::vector<int64_t>(mDisplays.begin(), mDisplays.end());
+}
+
+int32_t GraphicsComposerCallback::getInvalidHotplugCount() const {
+    std::scoped_lock lock(mMutex);
+    return mInvalidHotplugCount;
+}
+
+int32_t GraphicsComposerCallback::getInvalidRefreshCount() const {
+    std::scoped_lock lock(mMutex);
+    return mInvalidRefreshCount;
+}
+
+int32_t GraphicsComposerCallback::getInvalidVsyncCount() const {
+    std::scoped_lock lock(mMutex);
+    return mInvalidVsyncCount;
+}
+
+int32_t GraphicsComposerCallback::getInvalidVsyncPeriodChangeCount() const {
+    std::scoped_lock lock(mMutex);
+    return mInvalidVsyncPeriodChangeCount;
+}
+
+int32_t GraphicsComposerCallback::getInvalidSeamlessPossibleCount() const {
+    std::scoped_lock lock(mMutex);
+    return mInvalidSeamlessPossibleCount;
+}
+
+std::optional<VsyncPeriodChangeTimeline>
+GraphicsComposerCallback::takeLastVsyncPeriodChangeTimeline() {
+    std::scoped_lock lock(mMutex);
+
+    std::optional<VsyncPeriodChangeTimeline> ret;
+    ret.swap(mTimeline);
+
+    return ret;
+}
+
+::ndk::ScopedAStatus GraphicsComposerCallback::onHotplug(int64_t in_display, bool in_connected) {
+    std::scoped_lock lock(mMutex);
+    if (in_connected) {
+        if (!mDisplays.insert(in_display).second) {
+            mInvalidHotplugCount++;
+        }
+    } else {
+        if (!mDisplays.erase(in_display)) {
+            mInvalidHotplugCount++;
+        }
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus GraphicsComposerCallback::onRefresh(int64_t display) {
+    std::scoped_lock lock(mMutex);
+
+    if (mDisplays.count(display) == 0) {
+        mInvalidRefreshCount++;
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus GraphicsComposerCallback::onVsync(int64_t in_display, int64_t in_timestamp,
+                                                       int32_t in_vsyncPeriodNanos) {
+    std::scoped_lock lock(mMutex);
+    if (!mVsyncAllowed || mDisplays.count(in_display) == 0) {
+        mInvalidVsyncCount++;
+    }
+
+    ALOGV("%ld, %d", static_cast<long>(in_timestamp), in_vsyncPeriodNanos);
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus GraphicsComposerCallback::onVsyncPeriodTimingChanged(
+        int64_t in_display,
+        const ::aidl::android::hardware::graphics::composer3::VsyncPeriodChangeTimeline&
+                in_updatedTimeline) {
+    std::scoped_lock lock(mMutex);
+    if (mDisplays.count(in_display) == 0) {
+        mInvalidVsyncPeriodChangeCount++;
+    }
+    mTimeline = in_updatedTimeline;
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus GraphicsComposerCallback::onSeamlessPossible(int64_t in_display) {
+    std::scoped_lock lock(mMutex);
+    if (mDisplays.count(in_display)) {
+        mInvalidSeamlessPossibleCount++;
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::SpAIBinder GraphicsComposerCallback::asBinder() {
+    return nullptr;
+}
+
+bool GraphicsComposerCallback::isRemote() {
+    return true;
+}
+
+}  // namespace aidl::android::hardware::graphics::composer3::vts
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/GraphicsComposerCallback.h b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/GraphicsComposerCallback.h
new file mode 100644
index 0000000..9afc72f
--- /dev/null
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/GraphicsComposerCallback.h
@@ -0,0 +1,75 @@
+/**
+ * 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 <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
+
+#include <android-base/thread_annotations.h>
+#include <mutex>
+#include <unordered_set>
+
+namespace aidl::android::hardware::graphics::composer3::vts {
+
+// IComposerCallback to be installed with IComposerClient::registerCallback.
+class GraphicsComposerCallback : public IComposerCallback {
+  public:
+    void setVsyncAllowed(bool allowed);
+
+    std::vector<int64_t> getDisplays() const;
+
+    int32_t getInvalidHotplugCount() const;
+
+    int32_t getInvalidRefreshCount() const;
+
+    int32_t getInvalidVsyncCount() const;
+
+    int32_t getInvalidVsyncPeriodChangeCount() const;
+
+    int32_t getInvalidSeamlessPossibleCount() const;
+
+    std::optional<VsyncPeriodChangeTimeline> takeLastVsyncPeriodChangeTimeline();
+
+  private:
+    virtual ::ndk::ScopedAStatus onHotplug(int64_t in_display, bool in_connected) override;
+    virtual ::ndk::ScopedAStatus onRefresh(int64_t in_display) override;
+    virtual ::ndk::ScopedAStatus onSeamlessPossible(int64_t in_display) override;
+    virtual ::ndk::ScopedAStatus onVsync(int64_t in_display, int64_t in_timestamp,
+                                         int32_t in_vsyncPeriodNanos) override;
+    virtual ::ndk::ScopedAStatus onVsyncPeriodTimingChanged(
+            int64_t in_display,
+            const ::aidl::android::hardware::graphics::composer3::VsyncPeriodChangeTimeline&
+                    in_updatedTimeline) override;
+
+    ::ndk::SpAIBinder asBinder() override;
+    bool isRemote() override;
+
+    mutable std::mutex mMutex;
+    // the set of all currently connected displays
+    std::unordered_set<int64_t> mDisplays GUARDED_BY(mMutex);
+    // true only when vsync is enabled
+    bool mVsyncAllowed GUARDED_BY(mMutex) = true;
+
+    std::optional<VsyncPeriodChangeTimeline> mTimeline GUARDED_BY(mMutex);
+
+    // track invalid callbacks
+    int32_t mInvalidHotplugCount GUARDED_BY(mMutex) = 0;
+    int32_t mInvalidRefreshCount GUARDED_BY(mMutex) = 0;
+    int32_t mInvalidVsyncCount GUARDED_BY(mMutex) = 0;
+    int32_t mInvalidVsyncPeriodChangeCount GUARDED_BY(mMutex) = 0;
+    int32_t mInvalidSeamlessPossibleCount GUARDED_BY(mMutex) = 0;
+};
+
+}  // namespace aidl::android::hardware::graphics::composer3::vts
\ No newline at end of file
diff --git a/health/2.0/vts/functional/Android.bp b/health/2.0/vts/functional/Android.bp
index eb69612..0fcac19 100644
--- a/health/2.0/vts/functional/Android.bp
+++ b/health/2.0/vts/functional/Android.bp
@@ -32,5 +32,11 @@
         "android.hardware.health@1.0",
         "android.hardware.health@2.0",
     ],
-    test_suites: ["general-tests", "vts"],
+    header_libs: [
+        "libhealthtest_headers",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
 }
diff --git a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
index 7fabf2b..3afba45 100644
--- a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
+++ b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
@@ -29,6 +29,7 @@
 #include <android/hardware/health/2.0/types.h>
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
+#include <health-test/TestUtils.h>
 #include <hidl/GtestPrinter.h>
 #include <hidl/ServiceManagement.h>
 #include <log/log.h>
@@ -51,6 +52,7 @@
 namespace hardware {
 namespace health {
 
+using test_utils::SucceedOnce;
 using V1_0::BatteryStatus;
 using V1_0::toString;
 
@@ -356,64 +358,9 @@
                                   << toString(current.result) << ", skipping";
     }
 
-    // For IHealth.getCurrentNow/Average, if current is not available, it is expected that
-    // current.result == Result::NOT_SUPPORTED, which is checked above. Hence, zero current is
-    // not treated as unknown values.
-    // For IHealth.getHealthInfo, if current is not available, health_info.current_* == 0.
-    // Caller of this function provides current.result == Result::SUCCESS. Hence, just skip the
-    // check.
-    if (current.value == 0 && acceptZeroCurrentAsUnknown) {
-        return AssertionSuccess()
-               << "current is 0, which indicates the value may not be available. Skipping.";
-    }
-
-    switch (status.value) {
-        case BatteryStatus::UNKNOWN:
-            if (current.value != 0) {
-                // BatteryStatus may be UNKNOWN initially with a non-zero current value, but
-                // after it is initialized, it should be known.
-                return AssertionFailure()
-                       << "BatteryStatus is UNKNOWN but current is not 0. Actual: "
-                       << current.value;
-            }
-            break;
-        case BatteryStatus::CHARGING:
-            if (current.value <= 0) {
-                return AssertionFailure()
-                       << "BatteryStatus is CHARGING but current is not positive. Actual: "
-                       << current.value;
-            }
-            break;
-        case BatteryStatus::NOT_CHARGING:
-            if (current.value > 0) {
-                return AssertionFailure() << "BatteryStatus is " << toString(status.value)
-                                          << " but current is positive. Actual: " << current.value;
-            }
-            break;
-        case BatteryStatus::DISCHARGING:
-            if (current.value >= 0) {
-                return AssertionFailure()
-                       << "BatteryStatus is " << toString(status.value)
-                       << " but current is not negative. Actual: " << current.value;
-            }
-            break;
-        case BatteryStatus::FULL:
-            // Battery current may be positive or negative depending on the load.
-            break;
-        default:
-            return AssertionFailure() << "Unknown BatteryStatus " << toString(status.value);
-    }
-
-    return AssertionSuccess() << "BatteryStatus is " << toString(status.value)
-                              << " and current has the correct sign: " << current.value;
-}
-
-static AssertionResult IsValueSimilar(int32_t dividend, int32_t divisor, double factor) {
-    auto difference = abs(dividend - divisor);
-    if (difference > factor * abs(divisor)) {
-        return AssertionFailure() << dividend << " and " << divisor << " are not similar.";
-    }
-    return AssertionSuccess() << dividend << " and " << divisor << " are similar.";
+    return test_utils::IsBatteryCurrentSignCorrect(
+            status.value, current.value, acceptZeroCurrentAsUnknown,
+            [](BatteryStatus status) { return toString(status); });
 }
 
 static AssertionResult IsBatteryCurrentSimilar(HalResult<BatteryStatus> status,
@@ -437,31 +384,8 @@
                                   << currentAverage.value << ", skipping";
     }
 
-    // Check that the two values are similar. Note that the two tests uses a different
-    // divisor to ensure that they are actually pretty similar. For example,
-    // IsValueSimilar(5,10,0.4) returns true, but IsValueSimlar(10,5,0.4) returns false.
-    TEST_AND_RETURN(IsValueSimilar(currentNow.value, currentAverage.value, gCurrentCompareFactor)
-                    << " for now vs. average. Check units.");
-    TEST_AND_RETURN(IsValueSimilar(currentAverage.value, currentNow.value, gCurrentCompareFactor)
-                    << " for average vs. now. Check units.");
-    return AssertionSuccess() << "currentNow = " << currentNow.value
-                              << " and currentAverage = " << currentAverage.value
-                              << " are considered similar.";
-}
-
-// Test that f() returns AssertionSuccess() once in a given period of time.
-template <typename Duration, typename Function>
-static AssertionResult SucceedOnce(Duration d, Function f) {
-    AssertionResult result = AssertionFailure() << "Function never evaluated.";
-    auto end = std::chrono::system_clock::now() + d;
-    while (std::chrono::system_clock::now() <= end) {
-        result = f();
-        if (result) {
-            return result;
-        }
-        std::this_thread::sleep_for(2s);
-    }
-    return result;
+    return test_utils::IsBatteryCurrentSimilar(currentNow.value, currentAverage.value,
+                                               gCurrentCompareFactor);
 }
 
 uint64_t GetShippingApiLevel() {
@@ -603,40 +527,8 @@
     }
 
     const auto& batteryInfo = healthInfo.value.legacy;
-    bool isConnected = batteryInfo.chargerAcOnline || batteryInfo.chargerUsbOnline ||
-                       batteryInfo.chargerWirelessOnline;
-
-    std::stringstream message;
-    message << "BatteryStatus is " << toString(status.value) << " and "
-            << (isConnected ? "" : "no ")
-            << "power source is connected: ac=" << batteryInfo.chargerAcOnline
-            << ", usb=" << batteryInfo.chargerUsbOnline
-            << ", wireless=" << batteryInfo.chargerWirelessOnline;
-
-    switch (status.value) {
-        case BatteryStatus::UNKNOWN: {
-            // Don't enforce anything on isConnected on unknown battery status.
-            // Battery-less devices must report UNKNOWN battery status, but may report true
-            // or false on isConnected.
-        } break;
-        case BatteryStatus::CHARGING:
-        case BatteryStatus::NOT_CHARGING:
-        case BatteryStatus::FULL: {
-            if (!isConnected) {
-                return AssertionFailure() << message.str();
-            }
-        } break;
-        case BatteryStatus::DISCHARGING: {
-            if (isConnected) {
-                return AssertionFailure() << message.str();
-            }
-        } break;
-        default: {
-            return AssertionFailure() << "Unknown battery status value " << toString(status.value);
-        } break;
-    }
-
-    return AssertionSuccess() << message.str();
+    return test_utils::IsBatteryStatusCorrect(
+            status.value, batteryInfo, [](BatteryStatus status) { return toString(status); });
 }
 
 TEST_P(BatteryTest, ConnectedAgainstStatusFromHal) {
diff --git a/health/aidl/Android.bp b/health/aidl/Android.bp
new file mode 100644
index 0000000..fae7592
--- /dev/null
+++ b/health/aidl/Android.bp
@@ -0,0 +1,74 @@
+// 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.
+
+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"],
+}
+
+aidl_interface {
+    name: "android.hardware.health",
+    vendor_available: true,
+    recovery_available: true,
+    srcs: ["android/hardware/health/*.aidl"],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            enabled: true,
+            sdk_version: "module_current",
+        },
+        ndk: {
+            separate_platform_variant: false,
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
+
+cc_library {
+    name: "android.hardware.health-translate-ndk",
+    vendor_available: true,
+    recovery_available: true,
+    srcs: ["android/hardware/health/translate-ndk.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libhidlbase",
+        "android.hardware.health-V1-ndk",
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
+    export_include_dirs: ["include"],
+    export_shared_lib_headers: [
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
+}
+
+java_library {
+    name: "android.hardware.health-translate-java",
+    srcs: ["android/hardware/health/Translate.java"],
+    libs: [
+        "android.hardware.health-V1-java",
+        "android.hardware.health-V2.0-java",
+        "android.hardware.health-V2.1-java",
+    ],
+}
diff --git a/health/aidl/README.md b/health/aidl/README.md
new file mode 100644
index 0000000..53a4f91
--- /dev/null
+++ b/health/aidl/README.md
@@ -0,0 +1,293 @@
+# Health AIDL HAL
+
+## Determine whether the example service implementation is sufficient {#determine}
+
+You need a custom implementation if any of the following is true:
+
+* You are migrating from a custom
+  [health 2.1 HIDL HAL implementation](../2.1/README.md).
+* System properties `ro.charger.enable_suspend` and/or `ro.charger.no_ui`
+  are set to a `true` value. See [below](#charger-sysprops).
+* The device supports offline charging mode, and the `service`
+  declaration with `class charger` in `init.rc` is different from the one
+  provided by the example implementation. See [below](#charger-init-rc).
+
+If the example HAL service is sufficient, [install it](#use-example). Otherwise,
+[implement a custom HAL service](#use-custom).
+
+### System properties for charger {#charger-sysprops}
+
+The health AIDL HAL service also provides functionalities of `charger`. As a
+result, the system charger at `/system/bin/charger` is deprecated.
+
+However, the health AIDL HAL service is not allowed to read `ro.charger.*`
+system properties. These properties include:
+* `ro.charger.enable_suspend`. If set, you need a custom health AIDL HAL
+  service. See [below](#charger-enable-suspend).
+* `ro.charger.no_ui`. If set, you need a custom health AIDL HAL service.
+  See [below](#charger-no-ui).
+* `ro.charger.draw_split_screen`. The system property is deprecated.
+* `ro.charger.draw_split_offset`. The system property is deprecated.
+* `ro.charger.disable_init_blank`. The system property is deprecated.
+
+If you need to set any of the deprecated system properties, contact
+[OWNERS](OWNERS).
+
+### Default `service` declaration for charger in `init.rc` {#charger-init-rc}
+
+See
+[android.hardware.health-service.example.rc](default/android.hardware.health-service.example.rc).
+
+Check the `service` declaration in your device-specific `init.rc` file that
+has `class charger`. Most likely, the declaration looks something like this
+(Below is an excerpt from Pixel 3):
+
+```text
+service vendor.charger /system/bin/charger
+    class charger
+    seclabel u:r:charger:s0
+    user system
+    group system wakelock input
+    capabilities SYS_BOOT
+    file /dev/kmsg w
+    file /sys/fs/pstore/console-ramoops-0 r
+    file /sys/fs/pstore/console-ramoops r
+    file /proc/last_kmsg r
+```
+
+Compare each line against the one provided by the example health AIDL HAL
+service in
+[android.hardware.health-service.example.rc](default/android.hardware.health-service.example.rc).
+Specifically:
+
+* You may ignore the `service` line. The name of the service does not matter.
+* If your service belongs to additional classes beside `charger`, you need a
+  custom health AIDL service.
+* You may ignore the `seclabel` line. When the health AIDL service runs in
+  charger mode, its original SELinux domain is kept.
+* If your service has a different `user` (not `system`), you need a custom
+  health AIDL service.
+* If your service belongs to additional `group`s beside
+  `system wakelock input`, you need a custom health AIDL service.
+* If your service requires additional capabilities beside `SYS_BOOT`,
+  you need a custom health AIDL service.
+* If your service requires additional `file`s to be opened prior to execution,
+  you need a custom health AIDL service.
+
+## Using the example health AIDL HAL service {#use-example}
+
+If you [determined](#determine) that the example health AIDL HAL service works
+for your device, install it with
+
+```mk
+PRODUCT_PACKAGES += android.hardware.health-service.example
+```
+
+Then, delete any existing `service` with `class charger` in your device-specific
+`init.rc` files, because
+[android.hardware.health-service.example.rc](default/android.hardware.health-service.example.rc)
+already contains an entry for charger.
+
+If your device supports charger mode and it has custom charger resources,
+[move charger resources to `/vendor`](#charger-res)
+
+## Implementing a custom health AIDL HAL service {#use-custom}
+
+### Override the `Health` class {#health-impl}
+
+See [`Health.h`](default/include/health-impl/Health.h) for its class
+declaration. Inherit the class to customize for your device.
+
+```c++
+namespace aidl::android::hardware::health {
+class HealthImpl : public Health {
+    // ...
+};
+} // namespace aidl::android::hardware::health
+int main(int, char**) {
+    // ...
+    auto binder = ndk::SharedRefBase::make<aidl::android::hardware::health::HealthImpl>(
+            "default", std::move(config));
+    // ...
+}
+```
+
+* The logic to modify `healthd_config`, traditionally in `healthd_board_init()`
+  should be called before passing the `healthd_config` struct to your
+  `HealthImpl` class in [`main()`](#main).
+
+* The following functions are similar to the ones in the health 2.1 HIDL HAL:
+
+| AIDL implementation                 | HIDL implementation         |
+|-------------------------------------|-----------------------------|
+| `Health::getChargeCounterUah`       | `Health::getChargeCounter`  |
+| `Health::getCurrentNowMicroamps`    | `Health::getCurrentNow`     |
+| `Health::getCurrentAverageMicroamps`| `Health::getCurrentAverage` |
+| `Health::getCapacity`               | `Health::getCapacity`       |
+| `Health::getChargeStatus`           | `Health::getChargeStatus`   |
+| `Health::getEnergyCounterNwh`       | `Health::getEnergyCounter`  |
+| `Health::getDiskStats`              | `Health::getDiskStats`      |
+| `Health::getStorageInfo`            | `Health::getStorageInfo`    |
+| `Health::BinderEvent`               | `BinderHealth::BinderEvent` |
+| `Health::dump`                      | `Health::debug`             |
+| `Health::ShouldKeepScreenOn`        | `Health::shouldKeepScreenOn`|
+| `Health::UpdateHealthInfo`          | `Health::UpdateHealthInfo`  |
+
+### Implement `main()` {#main}
+
+See the [`main.cpp`](default/main.cpp) for the example health AIDL service for
+an example.
+
+If you need to modify `healthd_config`, do it before passing it to the
+constructor of `HealthImpl` (or `Health` if you did not implement a subclass
+of it).
+
+```c++
+int main(int argc, char** argv) {
+    auto config = std::make_unique<healthd_config>();
+    ::android::hardware::health::InitHealthdConfig(config.get());
+    healthd_board_init(config.get());
+    auto binder = ndk::SharedRefBase::make<Health>("default", std::move(config));
+    // ...
+}
+```
+
+If your device does not support off-line charging mode, or does not have a UI
+for charger (`ro.charger.no_ui=true`), skip the invocation of
+`ChargerModeMain()` in `main()`.
+
+### SELinux rules
+
+Add device specific permissions to the domain where the health HAL
+process is executed, especially if a device-specific `libhealthd` is used
+and/or device-specific storage related APIs are implemented.
+
+If you did not define a separate domain, the domain is likely
+`hal_health_default`. The device-specific rules for it is likely at
+`device/<manufacturer>/<device>/sepolicy/vendor/hal_health_default.te`.
+
+### Implementing charger {#charger}
+
+#### Move charger resources to `/vendor`
+
+Ensure that charger resources are installed to `/vendor`, not `/product`.
+
+`animation.txt` must be moved to the following location:
+
+```text
+/vendor/etc/res/values/charger/animation.txt
+```
+
+Charger resources in `/system` is not read by the health HAL service in
+`/vendor`. Specifically, resources should be installed to the following
+location:
+
+```
+/vendor/etc/res/images/charger/*.png
+```
+
+If resources are not found in these locations, the health HAL service falls
+back to the following locations:
+
+```
+/vendor/etc/res/images/charger/default/*.png
+```
+
+You can use the default resources by installing the default module:
+
+```makefile
+PRODUCT_PACKAGES += charger_res_images_vendor
+```
+
+#### Modify `init.rc` for charger
+
+It is recommended that you move the existing `service` entry with
+`class charger` to the `init.rc` file in your custom health service.
+
+Modify the entry to invoke the health service binary with `--charger` argument.
+See
+[android.hardware.health-service.example.rc](default/android.hardware.health-service.example.rc)
+for an example:
+
+```text
+service vendor.charger-tuna /vendor/bin/hw/android.hardware.health-service-tuna --charger
+    # ...
+```
+
+#### No charger mode {#no-charger}
+
+If your device does not support off-line charging mode, skip the invocation of
+`ChargerModeMain()` in `main()`.
+
+```c++
+int main(int, char**) {
+    // ...
+    // Skip checking if arguments contain "--charger"
+    auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
+    return hal_health_loop->StartLoop();
+}
+```
+
+You may optionally delete the `service` entry with `class charger` in the
+`init.rc` file.
+
+#### No charger UI {#charger-no-ui}
+
+If your device does not have a UI for charger (`ro.charger.no_ui=true`), skip
+the invocation of `ChargerModeMain()` in `main()`.
+
+You may want to keep the `KernelLogger` so that charger still logs battery
+information to the kernel logs.
+
+```c++
+int main(int argc, char** argv) {
+    // ...
+    if (argc >= 2 && argv[1] == "--charger"sv) {
+        android::base::InitLogging(argv, &android::base::KernelLogger);
+        // fallthrough to HalHealthLoop::StartLoop()
+    }
+    auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
+    return hal_health_loop->StartLoop();
+}
+```
+
+#### Enable suspend {#charger-enable-suspend}
+
+If your device has `ro.charger.enable_suspend=true`, implement a new class,
+`ChargerCallbackImpl`, that inherits from
+[`ChargerCallback`](default/include/health-impl/ChargerUtils.h). Then
+override the `ChargerEnableSuspend` function to return `true`. Then pass an
+instance of `ChargerCallbackImpl` to `ChargerModeMain()` instead.
+
+```c++
+namespace aidl::android::hardware::health {
+class ChargerCallbackImpl : public ChargerCallback {
+    bool ChargerEnableSuspend() override { return true; }
+};
+} // namespace aidl::android::hardware::health
+int main(int argc, char** argv) {
+    // ...
+    if (argc >= 2 && argv[1] == "--charger"sv) {
+        android::base::InitLogging(argv, &android::base::KernelLogger);
+#if !CHARGER_FORCE_NO_UI
+        return ChargerModeMain(binder,
+                std::make_shared<aidl::android::hardware::health::ChargerCallbackImpl>(binder));
+#endif
+    }
+    // ...
+}
+```
+
+#### SELinux rules for charger
+
+If your health AIDL service runs in a domain other than `hal_health_default`,
+add `charger_type` to it so the health HAL service can have charger-specific
+permissions. Example (assuming that your health AIDL service runs in domain
+`hal_health_tuna`:
+
+```text
+type hal_health_tuna, charger_type, domain;
+hal_server_domain(hal_health_default, hal_health)
+```
+
+[comment: TODO(b/170338625): explain recovery]: #
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryCapacityLevel.aidl
similarity index 90%
rename from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
rename to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryCapacityLevel.aidl
index 2b872ab..e543886 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryCapacityLevel.aidl
@@ -31,14 +31,14 @@
 // 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;
+package android.hardware.health;
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum BatteryCapacityLevel {
+  UNSUPPORTED = -1,
+  UNKNOWN = 0,
+  CRITICAL = 1,
+  LOW = 2,
+  NORMAL = 3,
+  HIGH = 4,
+  FULL = 5,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryHealth.aidl
similarity index 90%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryHealth.aidl
index 2b872ab..4ce7952 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryHealth.aidl
@@ -31,14 +31,14 @@
 // 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;
+package android.hardware.health;
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum BatteryHealth {
+  UNKNOWN = 1,
+  GOOD = 2,
+  OVERHEAT = 3,
+  DEAD = 4,
+  OVER_VOLTAGE = 5,
+  UNSPECIFIED_FAILURE = 6,
+  COLD = 7,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryStatus.aidl
similarity index 90%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryStatus.aidl
index 2b872ab..340b2ec 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/BatteryStatus.aidl
@@ -31,14 +31,12 @@
 // 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;
+package android.hardware.health;
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum BatteryStatus {
+  UNKNOWN = 1,
+  CHARGING = 2,
+  DISCHARGING = 3,
+  NOT_CHARGING = 4,
+  FULL = 5,
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/DiskStats.aidl
similarity index 85%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/DiskStats.aidl
index 2b872ab..5aa5890 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/DiskStats.aidl
@@ -31,14 +31,18 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.health;
+@VintfStability
+parcelable DiskStats {
+  long reads;
+  long readMerges;
+  long readSectors;
+  long readTicks;
+  long writes;
+  long writeMerges;
+  long writeSectors;
+  long writeTicks;
+  long ioInFlight;
+  long ioTicks;
+  long ioInQueue;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/HealthInfo.aidl
similarity index 63%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/HealthInfo.aidl
index 2b872ab..34a87a6 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/HealthInfo.aidl
@@ -31,14 +31,30 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.health;
+@VintfStability
+parcelable HealthInfo {
+  boolean chargerAcOnline;
+  boolean chargerUsbOnline;
+  boolean chargerWirelessOnline;
+  int maxChargingCurrentMicroamps;
+  int maxChargingVoltageMicrovolts;
+  android.hardware.health.BatteryStatus batteryStatus;
+  android.hardware.health.BatteryHealth batteryHealth;
+  boolean batteryPresent;
+  int batteryLevel;
+  int batteryVoltageMillivolts;
+  int batteryTemperatureTenthsCelsius;
+  int batteryCurrentMicroamps;
+  int batteryCycleCount;
+  int batteryFullChargeUah;
+  int batteryChargeCounterUah;
+  String batteryTechnology;
+  int batteryCurrentAverageMicroamps;
+  android.hardware.health.DiskStats[] diskStats;
+  android.hardware.health.StorageInfo[] storageInfos;
+  android.hardware.health.BatteryCapacityLevel batteryCapacityLevel;
+  long batteryChargeTimeToFullNowSeconds;
+  int batteryFullChargeDesignCapacityUah;
+  const int BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED = -1;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealth.aidl
similarity index 70%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealth.aidl
index 2b872ab..7016ae4 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealth.aidl
@@ -31,14 +31,21 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.health;
+@VintfStability
+interface IHealth {
+  void registerCallback(in android.hardware.health.IHealthInfoCallback callback);
+  void unregisterCallback(in android.hardware.health.IHealthInfoCallback callback);
+  void update();
+  int getChargeCounterUah();
+  int getCurrentNowMicroamps();
+  int getCurrentAverageMicroamps();
+  int getCapacity();
+  long getEnergyCounterNwh();
+  android.hardware.health.BatteryStatus getChargeStatus();
+  android.hardware.health.StorageInfo[] getStorageInfo();
+  android.hardware.health.DiskStats[] getDiskStats();
+  android.hardware.health.HealthInfo getHealthInfo();
+  const int STATUS_UNKNOWN = 2;
+  const int STATUS_CALLBACK_DIED = 4;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealthInfoCallback.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealthInfoCallback.aidl
index 2b872ab..1b6366f 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/IHealthInfoCallback.aidl
@@ -31,14 +31,8 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.health;
+@VintfStability
+interface IHealthInfoCallback {
+  oneway void healthInfoChanged(in android.hardware.health.HealthInfo info);
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/StorageInfo.aidl
similarity index 88%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/StorageInfo.aidl
index 2b872ab..eaae5a6 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/health/aidl/aidl_api/android.hardware.health/current/android/hardware/health/StorageInfo.aidl
@@ -31,14 +31,11 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.health;
+@VintfStability
+parcelable StorageInfo {
+  int eol;
+  int lifetimeA;
+  int lifetimeB;
+  String version;
 }
diff --git a/health/aidl/android/hardware/health/BatteryCapacityLevel.aidl b/health/aidl/android/hardware/health/BatteryCapacityLevel.aidl
new file mode 100644
index 0000000..0c26fa2
--- /dev/null
+++ b/health/aidl/android/hardware/health/BatteryCapacityLevel.aidl
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+/**
+ * Battery capacity level. This enum provides additional information along side
+ * with the battery capacity.
+ * Clients of this HAL must use this value before inferring it from the
+ * battery capacity.
+ */
+@VintfStability
+@Backing(type="int")
+enum BatteryCapacityLevel {
+    /**
+     * Battery capacity level is unsupported.
+     * Battery capacity level must be set to this value if and only if the
+     * implementation is unsupported.
+     */
+    UNSUPPORTED = -1,
+    /**
+     * Battery capacity level is unknown.
+     * Battery capacity level must be set to this value if and only if battery
+     * is not present or the battery capacity level is unknown/uninitialized.
+     */
+    UNKNOWN,
+    /**
+     * Battery is at critical level. The Android framework must schedule a
+     * shutdown when it sees this value from the HAL.
+     */
+    CRITICAL,
+    /**
+     * Battery is low. The Android framework may limit the performance of
+     * the device when it sees this value from the HAL.
+     */
+    LOW,
+    /**
+     * Battery level is normal.
+     */
+    NORMAL,
+    /**
+     * Battery level is high.
+     */
+    HIGH,
+    /**
+     * Battery is full. It must be set to FULL if and only if battery level is
+     * 100.
+     */
+    FULL,
+}
diff --git a/health/aidl/android/hardware/health/BatteryHealth.aidl b/health/aidl/android/hardware/health/BatteryHealth.aidl
new file mode 100644
index 0000000..2b6e51f
--- /dev/null
+++ b/health/aidl/android/hardware/health/BatteryHealth.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+/**
+ * Possible values for Battery Health.
+ * Note: These are currently in sync with BatteryManager and must not
+ * be extended / altered.
+ */
+@VintfStability
+@Backing(type="int")
+enum BatteryHealth {
+    UNKNOWN = 1,
+    GOOD = 2,
+    OVERHEAT = 3,
+    DEAD = 4,
+    OVER_VOLTAGE = 5,
+    /**
+     * Battery experienced an unknown/unspecified failure.
+     */
+    UNSPECIFIED_FAILURE = 6,
+    COLD = 7,
+}
diff --git a/health/aidl/android/hardware/health/BatteryStatus.aidl b/health/aidl/android/hardware/health/BatteryStatus.aidl
new file mode 100644
index 0000000..774b28e
--- /dev/null
+++ b/health/aidl/android/hardware/health/BatteryStatus.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+/**
+ * Possible values for Battery Status.
+ * Note: These are currently in sync with BatteryManager and must not
+ * be extended / altered.
+ */
+@VintfStability
+@Backing(type="int")
+enum BatteryStatus {
+    UNKNOWN = 1,
+    CHARGING = 2,
+    DISCHARGING = 3,
+    /**
+     * Battery is *not* charging - special case when charger is present
+     * but battery isn't charging
+     */
+    NOT_CHARGING = 4,
+    FULL = 5,
+}
diff --git a/health/aidl/android/hardware/health/DiskStats.aidl b/health/aidl/android/hardware/health/DiskStats.aidl
new file mode 100644
index 0000000..532cbfd
--- /dev/null
+++ b/health/aidl/android/hardware/health/DiskStats.aidl
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+/*
+ * Disk statistics since boot.
+ *
+ * See {@code struct disk_stats} in {@code storaged} for interpretations of these fields.
+ *
+ * All integers in this struct must be interpreted as unsigned.
+ */
+@VintfStability
+parcelable DiskStats {
+    /**
+     * Number of reads processed.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long reads;
+    /**
+     * number of read I/Os merged with in-queue I/Os.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long readMerges;
+    /**
+     * number of sectors read.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long readSectors;
+    /**
+     * total wait time for read requests.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long readTicks;
+    /**
+     * number of writes processed.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long writes;
+    /**
+     * number of writes merged with in-queue I/Os.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long writeMerges;
+    /**
+     * number of sectors written.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long writeSectors;
+    /**
+     * total wait time for write requests.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long writeTicks;
+    /**
+     * number of I/Os currently in flight.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long ioInFlight;
+    /**
+     * total time this block device has been active.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long ioTicks;
+    /**
+     * total wait time for all requests.
+     *
+     * Value must be interpreted as unsigned.
+     */
+    long ioInQueue;
+}
diff --git a/health/aidl/android/hardware/health/HealthInfo.aidl b/health/aidl/android/hardware/health/HealthInfo.aidl
new file mode 100644
index 0000000..504e218
--- /dev/null
+++ b/health/aidl/android/hardware/health/HealthInfo.aidl
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+import android.hardware.health.BatteryCapacityLevel;
+import android.hardware.health.BatteryHealth;
+import android.hardware.health.BatteryStatus;
+import android.hardware.health.DiskStats;
+import android.hardware.health.StorageInfo;
+
+/**
+ * Health Information.
+ */
+@VintfStability
+parcelable HealthInfo {
+    /**
+     * AC charger state - 'true' if online
+     */
+    boolean chargerAcOnline;
+    /**
+     * USB charger state - 'true' if online
+     */
+    boolean chargerUsbOnline;
+    /**
+     * Wireless charger state - 'true' if online
+     */
+    boolean chargerWirelessOnline;
+    /**
+     * Maximum charging current supported by charger in µA
+     */
+    int maxChargingCurrentMicroamps;
+    /**
+     * Maximum charging voltage supported by charger in µV
+     */
+    int maxChargingVoltageMicrovolts;
+
+    android.hardware.health.BatteryStatus batteryStatus;
+
+    android.hardware.health.BatteryHealth batteryHealth;
+    /**
+     * 'true' if battery is present
+     */
+    boolean batteryPresent;
+    /**
+     * Remaining battery capacity in percent
+     */
+    int batteryLevel;
+    /**
+     * Instantaneous battery voltage in millivolts (mV).
+     *
+     * Historically, the unit of this field is microvolts (µV), but all
+     * clients and implementations uses millivolts in practice, making it
+     * the de-facto standard.
+     */
+    int batteryVoltageMillivolts;
+    /**
+     * Instantaneous battery temperature in tenths of degrees Celsius
+     */
+    int batteryTemperatureTenthsCelsius;
+    /**
+     * Instantaneous battery current in µA
+     */
+    int batteryCurrentMicroamps;
+    /**
+     * Battery charge cycle count
+     */
+    int batteryCycleCount;
+    /**
+     * Battery charge value when it is considered to be "full" in µA-h
+     */
+    int batteryFullChargeUah;
+    /**
+     * Instantaneous battery capacity in µA-h
+     */
+    int batteryChargeCounterUah;
+    /**
+     * Battery technology, e.g. "Li-ion, Li-Poly" etc.
+     */
+    String batteryTechnology;
+    /**
+     * Average battery current in µA. Will be 0 if unsupported.
+     */
+    int batteryCurrentAverageMicroamps;
+    /**
+     * Disk Statistics. Will be an empty vector if unsupported.
+     */
+    DiskStats[] diskStats;
+    /**
+     * Information on storage devices. Will be an empty vector if
+     * unsupported.
+     */
+    StorageInfo[] storageInfos;
+    /**
+     * Battery capacity level. See {@link BatteryCapacityLevel} for more details.
+     */
+    BatteryCapacityLevel batteryCapacityLevel;
+
+    /**
+     * Value of {@link #batteryChargeTimeToFullNowSeconds} if it is not
+     * supported.
+     */
+    const int BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED = -1;
+    /**
+     * Estimated time to fully charge the device (in seconds).
+     * Value must be BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED if and
+     * only if the implementation is unsupported.
+     * Value must be 0 if and only if batteryCapacityLevel is FULL or UNKNOWN.
+     * Otherwise, value must be positive.
+     */
+    long batteryChargeTimeToFullNowSeconds;
+    /**
+     * Estimated battery full charge design capacity (in microamp hours, µAh).
+     * Value must be 0 if unknown.
+     * Value must be greater than 100 000 µAh if known.
+     * Value must be less than 100 000 000 µAh if known.
+     */
+    int batteryFullChargeDesignCapacityUah;
+}
diff --git a/health/aidl/android/hardware/health/IHealth.aidl b/health/aidl/android/hardware/health/IHealth.aidl
new file mode 100644
index 0000000..d541eca
--- /dev/null
+++ b/health/aidl/android/hardware/health/IHealth.aidl
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+import android.hardware.health.BatteryStatus;
+import android.hardware.health.DiskStats;
+import android.hardware.health.HealthInfo;
+import android.hardware.health.IHealthInfoCallback;
+import android.hardware.health.StorageInfo;
+
+/**
+ * IHealth manages health info and posts events on registered callbacks.
+ *
+ * Implementations must send health info to all callbacks periodically.
+ */
+@VintfStability
+interface IHealth {
+    /** Status code for function. The operation encounters an unknown error. */
+    const int STATUS_UNKNOWN = 2;
+
+    /**
+     * Status code for function.
+     * A registered callback object is dead.
+     */
+    const int STATUS_CALLBACK_DIED = 4;
+
+    /**
+     * Register a callback for any health info events.
+     *
+     * Registering a new callback must not unregister the old one; the old
+     * callback remains registered until one of the following happens:
+     * - A client explicitly calls {@link #unregisterCallback} to unregister it.
+     * - The client process that hosts the callback dies.
+     *
+     * @param callback the callback to register.
+     * @return If error, return service specific error with code STATUS_UNKNOWN.
+     */
+    void registerCallback(in IHealthInfoCallback callback);
+
+    /**
+     * Explicitly unregister a callback that is previously registered through
+     * {@link #registerCallback}.
+     *
+     * @param callback the callback to unregister.
+     * @return If error:
+     *         - Return exception with code EX_ILLEGAL_ARGUMENT
+     *           if callback is not registered previously,
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for other errors.
+     */
+    void unregisterCallback(in IHealthInfoCallback callback);
+
+    /**
+     * Schedule update.
+     *
+     * When update() is called, the service must notify all registered callbacks
+     * with the most recent health info.
+     *
+     * @return If error, return service specific error with code:
+     *         - STATUS_CALLBACK_DIED if any registered callback is dead,
+     *         - STATUS_UNKNOWN for other errors.
+     */
+    void update();
+
+    /**
+     * Get battery capacity in microampere-hours(µAh).
+     *
+     * @return battery capacity if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported
+     *           (e.g. the file that stores this property does not exist),
+     *         - Retrurn service specific error with code
+     *           STATUS_UNKNOWN for other errors.
+     */
+    int getChargeCounterUah();
+
+    /**
+     * Get instantaneous battery current in microamperes(µA).
+     *
+     * Positive values indicate net current entering the battery from a charge
+     * source, negative values indicate net current discharging from the
+     * battery.
+     *
+     * @return instantaneous battery current if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported
+     *                 (e.g. the file that stores this property does not exist),
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for for other errors.
+     */
+    int getCurrentNowMicroamps();
+
+    /**
+     * Get average battery current in microamperes(µA).
+     *
+     * Positive values indicate net current entering the battery from a charge
+     * source, negative values indicate net current discharging from the
+     * battery. The time period over which the average is computed may depend on
+     * the fuel gauge hardware and its configuration.
+     *
+     * @return average battery current if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported
+     *                 (e.g. the file that stores this property does not exist),
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for for other errors.
+     */
+    int getCurrentAverageMicroamps();
+
+    /**
+     * Get remaining battery capacity percentage of total capacity
+     * (with no fractional part).
+     *
+     * @return remaining battery capacity if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported
+     *                 (e.g. the file that stores this property does not exist),
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for for other errors.
+     */
+    int getCapacity();
+
+    /**
+     * Get battery remaining energy in nanowatt-hours.
+     *
+     * @return remaining energy if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported,
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for for other errors.
+     */
+    long getEnergyCounterNwh();
+
+    /**
+     * Get battery charge status.
+     *
+     * @return charge status if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported
+     *                 (e.g. the file that stores this property does not exist),
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for other errors.
+     */
+    BatteryStatus getChargeStatus();
+
+    /**
+     * Get storage info.
+     *
+     * @return vector of StorageInfo structs if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported,
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for other errors.
+     */
+    StorageInfo[] getStorageInfo();
+
+    /**
+     * Gets disk statistics (number of reads/writes processed, number of I/O
+     * operations in flight etc).
+     *
+     * @return vector of disk statistics if successful.
+     *         The mapping is index 0->sda, 1->sdb and so on.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this property is not supported,
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for other errors.
+     */
+    DiskStats[] getDiskStats();
+
+    /**
+     * Get Health Information.
+     *
+     * @return Health information if successful.
+     *         If error:
+     *         - Return exception with code EX_UNSUPPORTED_OPERATION
+     *           if this API is not supported,
+     *         - Return service specific error with code STATUS_UNKNOWN
+     *           for for other errors.
+     */
+    HealthInfo getHealthInfo();
+}
diff --git a/health/aidl/android/hardware/health/IHealthInfoCallback.aidl b/health/aidl/android/hardware/health/IHealthInfoCallback.aidl
new file mode 100644
index 0000000..e9720fa
--- /dev/null
+++ b/health/aidl/android/hardware/health/IHealthInfoCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+import android.hardware.health.HealthInfo;
+
+/**
+ * IHealthInfoCallback is the updated callback interface to
+ * {@link IHealth#registerCallback}.
+ */
+@VintfStability
+interface IHealthInfoCallback {
+    /**
+     * An implementation of IHealth must call healthInfoChanged on all
+     * registered callbacks after health info changes.
+     * @param info the updated HealthInfo
+     */
+    oneway void healthInfoChanged(in HealthInfo info);
+}
diff --git a/health/aidl/android/hardware/health/StorageInfo.aidl b/health/aidl/android/hardware/health/StorageInfo.aidl
new file mode 100644
index 0000000..4c4dace
--- /dev/null
+++ b/health/aidl/android/hardware/health/StorageInfo.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+/*
+ * Information on storage device including life time estimates, end of life
+ * information and other attributes.
+ *
+ * All integers in this struct must be interpreted as non-negative.
+ */
+@VintfStability
+parcelable StorageInfo {
+    /**
+     * pre-eol (end of life) information. Follows JEDEC standard No.84-B50.
+     *
+     * Value must be interpreted as non-negative.
+     */
+    int eol;
+    /**
+     * device life time estimation (type A). Follows JEDEC standard No.84-B50.
+     *
+     * Value must be interpreted as non-negative.
+     */
+    int lifetimeA;
+    /**
+     * device life time estimation (type B). Follows JEDEC standard No.84-B50.
+     *
+     * Value must be interpreted as non-negative.
+     */
+    int lifetimeB;
+    /**
+     * version string
+     */
+    String version;
+}
diff --git a/health/aidl/android/hardware/health/Translate.java b/health/aidl/android/hardware/health/Translate.java
new file mode 100644
index 0000000..c8ace1c
--- /dev/null
+++ b/health/aidl/android/hardware/health/Translate.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package android.hardware.health;
+
+public class Translate {
+    static public android.hardware.health.StorageInfo h2aTranslate(
+            android.hardware.health.V2_0.StorageInfo in) {
+        android.hardware.health.StorageInfo out = new android.hardware.health.StorageInfo();
+        out.eol = in.eol;
+        out.lifetimeA = in.lifetimeA;
+        out.lifetimeB = in.lifetimeB;
+        out.version = in.version;
+        return out;
+    }
+
+    static public android.hardware.health.DiskStats h2aTranslate(
+            android.hardware.health.V2_0.DiskStats in) {
+        android.hardware.health.DiskStats out = new android.hardware.health.DiskStats();
+        out.reads = in.reads;
+        out.readMerges = in.readMerges;
+        out.readSectors = in.readSectors;
+        out.readTicks = in.readTicks;
+        out.writes = in.writes;
+        out.writeMerges = in.writeMerges;
+        out.writeSectors = in.writeSectors;
+        out.writeTicks = in.writeTicks;
+        out.ioInFlight = in.ioInFlight;
+        out.ioTicks = in.ioTicks;
+        out.ioInQueue = in.ioInQueue;
+        return out;
+    }
+
+    static public android.hardware.health.HealthInfo h2aTranslate(
+            android.hardware.health.V2_1.HealthInfo in) {
+        android.hardware.health.HealthInfo out = new android.hardware.health.HealthInfo();
+        out.chargerAcOnline = in.legacy.legacy.chargerAcOnline;
+        out.chargerUsbOnline = in.legacy.legacy.chargerUsbOnline;
+        out.chargerWirelessOnline = in.legacy.legacy.chargerWirelessOnline;
+        out.maxChargingCurrentMicroamps = in.legacy.legacy.maxChargingCurrent;
+        out.maxChargingVoltageMicrovolts = in.legacy.legacy.maxChargingVoltage;
+        out.batteryStatus = in.legacy.legacy.batteryStatus;
+        out.batteryHealth = in.legacy.legacy.batteryHealth;
+        out.batteryPresent = in.legacy.legacy.batteryPresent;
+        out.batteryLevel = in.legacy.legacy.batteryLevel;
+        out.batteryVoltageMillivolts = in.legacy.legacy.batteryVoltage;
+        out.batteryTemperatureTenthsCelsius = in.legacy.legacy.batteryTemperature;
+        out.batteryCurrentMicroamps = in.legacy.legacy.batteryCurrent;
+        out.batteryCycleCount = in.legacy.legacy.batteryCycleCount;
+        out.batteryFullChargeUah = in.legacy.legacy.batteryFullCharge;
+        out.batteryChargeCounterUah = in.legacy.legacy.batteryChargeCounter;
+        out.batteryTechnology = in.legacy.legacy.batteryTechnology;
+        out.batteryCurrentAverageMicroamps = in.legacy.batteryCurrentAverage;
+        out.diskStats = new android.hardware.health.DiskStats[in.legacy.diskStats.size()];
+        for (int i = 0; i < in.legacy.diskStats.size(); i++) {
+            out.diskStats[i] = h2aTranslate(in.legacy.diskStats.get(i));
+        }
+        out.storageInfos = new android.hardware.health.StorageInfo[in.legacy.storageInfos.size()];
+        for (int i = 0; i < in.legacy.storageInfos.size(); i++) {
+            out.storageInfos[i] = h2aTranslate(in.legacy.storageInfos.get(i));
+        }
+        out.batteryCapacityLevel = in.batteryCapacityLevel;
+        out.batteryChargeTimeToFullNowSeconds = in.batteryChargeTimeToFullNowSeconds;
+        out.batteryFullChargeDesignCapacityUah = in.batteryFullChargeDesignCapacityUah;
+        return out;
+    }
+}
diff --git a/health/aidl/android/hardware/health/translate-ndk.cpp b/health/aidl/android/hardware/health/translate-ndk.cpp
new file mode 100644
index 0000000..7fe6ced
--- /dev/null
+++ b/health/aidl/android/hardware/health/translate-ndk.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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/hardware/health/translate-ndk.h"
+
+namespace android::h2a {
+
+static_assert(aidl::android::hardware::health::BatteryStatus::UNKNOWN ==
+              static_cast<aidl::android::hardware::health::BatteryStatus>(
+                      ::android::hardware::health::V1_0::BatteryStatus::UNKNOWN));
+static_assert(aidl::android::hardware::health::BatteryStatus::CHARGING ==
+              static_cast<aidl::android::hardware::health::BatteryStatus>(
+                      ::android::hardware::health::V1_0::BatteryStatus::CHARGING));
+static_assert(aidl::android::hardware::health::BatteryStatus::DISCHARGING ==
+              static_cast<aidl::android::hardware::health::BatteryStatus>(
+                      ::android::hardware::health::V1_0::BatteryStatus::DISCHARGING));
+static_assert(aidl::android::hardware::health::BatteryStatus::NOT_CHARGING ==
+              static_cast<aidl::android::hardware::health::BatteryStatus>(
+                      ::android::hardware::health::V1_0::BatteryStatus::NOT_CHARGING));
+static_assert(aidl::android::hardware::health::BatteryStatus::FULL ==
+              static_cast<aidl::android::hardware::health::BatteryStatus>(
+                      ::android::hardware::health::V1_0::BatteryStatus::FULL));
+
+static_assert(aidl::android::hardware::health::BatteryHealth::UNKNOWN ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::UNKNOWN));
+static_assert(aidl::android::hardware::health::BatteryHealth::GOOD ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::GOOD));
+static_assert(aidl::android::hardware::health::BatteryHealth::OVERHEAT ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::OVERHEAT));
+static_assert(aidl::android::hardware::health::BatteryHealth::DEAD ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::DEAD));
+static_assert(aidl::android::hardware::health::BatteryHealth::OVER_VOLTAGE ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::OVER_VOLTAGE));
+static_assert(aidl::android::hardware::health::BatteryHealth::UNSPECIFIED_FAILURE ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::UNSPECIFIED_FAILURE));
+static_assert(aidl::android::hardware::health::BatteryHealth::COLD ==
+              static_cast<aidl::android::hardware::health::BatteryHealth>(
+                      ::android::hardware::health::V1_0::BatteryHealth::COLD));
+
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::UNSUPPORTED ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::UNSUPPORTED));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::UNKNOWN ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::UNKNOWN));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::CRITICAL ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::CRITICAL));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::LOW ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::LOW));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::NORMAL ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::NORMAL));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::HIGH ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::HIGH));
+static_assert(aidl::android::hardware::health::BatteryCapacityLevel::FULL ==
+              static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+                      ::android::hardware::health::V2_1::BatteryCapacityLevel::FULL));
+
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_0::StorageInfo& in,
+        aidl::android::hardware::health::StorageInfo* out) {
+    out->eol = in.eol;
+    out->lifetimeA = in.lifetimeA;
+    out->lifetimeB = in.lifetimeB;
+    out->version = in.version;
+    return true;
+}
+
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_0::DiskStats& in,
+        aidl::android::hardware::health::DiskStats* out) {
+    out->reads = static_cast<int64_t>(in.reads);
+    out->readMerges = static_cast<int64_t>(in.readMerges);
+    out->readSectors = static_cast<int64_t>(in.readSectors);
+    out->readTicks = static_cast<int64_t>(in.readTicks);
+    out->writes = static_cast<int64_t>(in.writes);
+    out->writeMerges = static_cast<int64_t>(in.writeMerges);
+    out->writeSectors = static_cast<int64_t>(in.writeSectors);
+    out->writeTicks = static_cast<int64_t>(in.writeTicks);
+    out->ioInFlight = static_cast<int64_t>(in.ioInFlight);
+    out->ioTicks = static_cast<int64_t>(in.ioTicks);
+    out->ioInQueue = static_cast<int64_t>(in.ioInQueue);
+    return true;
+}
+
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_1::HealthInfo& in,
+        aidl::android::hardware::health::HealthInfo* out) {
+    out->chargerAcOnline = static_cast<bool>(in.legacy.legacy.chargerAcOnline);
+    out->chargerUsbOnline = static_cast<bool>(in.legacy.legacy.chargerUsbOnline);
+    out->chargerWirelessOnline = static_cast<bool>(in.legacy.legacy.chargerWirelessOnline);
+    out->maxChargingCurrentMicroamps = static_cast<int32_t>(in.legacy.legacy.maxChargingCurrent);
+    out->maxChargingVoltageMicrovolts = static_cast<int32_t>(in.legacy.legacy.maxChargingVoltage);
+    out->batteryStatus = static_cast<aidl::android::hardware::health::BatteryStatus>(
+            in.legacy.legacy.batteryStatus);
+    out->batteryHealth = static_cast<aidl::android::hardware::health::BatteryHealth>(
+            in.legacy.legacy.batteryHealth);
+    out->batteryPresent = static_cast<bool>(in.legacy.legacy.batteryPresent);
+    out->batteryLevel = static_cast<int32_t>(in.legacy.legacy.batteryLevel);
+    out->batteryVoltageMillivolts = static_cast<int32_t>(in.legacy.legacy.batteryVoltage);
+    out->batteryTemperatureTenthsCelsius =
+            static_cast<int32_t>(in.legacy.legacy.batteryTemperature);
+    out->batteryCurrentMicroamps = static_cast<int32_t>(in.legacy.legacy.batteryCurrent);
+    out->batteryCycleCount = static_cast<int32_t>(in.legacy.legacy.batteryCycleCount);
+    out->batteryFullChargeUah = static_cast<int32_t>(in.legacy.legacy.batteryFullCharge);
+    out->batteryChargeCounterUah = static_cast<int32_t>(in.legacy.legacy.batteryChargeCounter);
+    out->batteryTechnology = in.legacy.legacy.batteryTechnology;
+    out->batteryCurrentAverageMicroamps = static_cast<int32_t>(in.legacy.batteryCurrentAverage);
+    out->diskStats.clear();
+    out->diskStats.resize(in.legacy.diskStats.size());
+    for (size_t i = 0; i < in.legacy.diskStats.size(); ++i)
+        if (!translate(in.legacy.diskStats[i], &out->diskStats[i])) return false;
+    out->storageInfos.clear();
+    out->storageInfos.resize(in.legacy.storageInfos.size());
+    for (size_t i = 0; i < in.legacy.storageInfos.size(); ++i)
+        if (!translate(in.legacy.storageInfos[i], &out->storageInfos[i])) return false;
+    out->batteryCapacityLevel = static_cast<aidl::android::hardware::health::BatteryCapacityLevel>(
+            in.batteryCapacityLevel);
+    out->batteryChargeTimeToFullNowSeconds =
+            static_cast<int64_t>(in.batteryChargeTimeToFullNowSeconds);
+    out->batteryFullChargeDesignCapacityUah =
+            static_cast<int32_t>(in.batteryFullChargeDesignCapacityUah);
+    return true;
+}
+
+}  // namespace android::h2a
diff --git a/health/aidl/default/Android.bp b/health/aidl/default/Android.bp
new file mode 100644
index 0000000..a13c677
--- /dev/null
+++ b/health/aidl/default/Android.bp
@@ -0,0 +1,141 @@
+// 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.
+
+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_defaults {
+    name: "libhealth_aidl_common_defaults",
+    recovery_available: true,
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "liblog",
+        "libutils",
+        "android.hardware.health-V1-ndk",
+
+        // TODO(b/177269435): remove when BatteryMonitor works with AIDL HealthInfo.
+        "libhidlbase",
+    ],
+    static_libs: [
+        "libbatterymonitor",
+        "libhealthloop",
+
+        // TODO(b/177269435): remove when BatteryMonitor works with AIDL HealthInfo.
+        "android.hardware.health-translate-ndk",
+    ],
+}
+
+// Dependency to libhealthd_charger_ui. No UI in recovery.
+cc_defaults {
+    name: "libhealth_aidl_charger_defaults",
+    shared_libs: [
+        // common
+        "android.hardware.health-V1-ndk",
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+
+        // charger UI only
+        "libpng",
+    ],
+
+    static_libs: [
+        // common
+        "libbatterymonitor",
+        "libhealthloop",
+
+        // charger UI only
+        "libhealthd_draw",
+        "libhealthd_charger_ui",
+        "libminui",
+        "libsuspend",
+    ],
+
+    target: {
+        recovery: {
+            // No UI and libsuspend for recovery charger.
+            cflags: [
+                "-DCHARGER_FORCE_NO_UI=1",
+            ],
+            exclude_shared_libs: [
+                "libpng",
+            ],
+            exclude_static_libs: [
+                "libhealthd_draw",
+                "libhealthd_charger_ui",
+                "libminui",
+                "libsuspend",
+            ],
+        },
+    },
+}
+
+// AIDL version of libhealth2impl.
+// A helper library for health HAL implementation.
+// HAL implementations can link to this library and extend the Health class.
+cc_library_static {
+    name: "libhealth_aidl_impl",
+    defaults: [
+        "libhealth_aidl_common_defaults",
+        "libhealth_aidl_charger_defaults",
+    ],
+    export_include_dirs: ["include"],
+    export_static_lib_headers: [
+        "libbatterymonitor",
+    ],
+    srcs: [
+        "ChargerUtils.cpp",
+        "health-convert.cpp",
+        "HalHealthLoop.cpp",
+        "Health.cpp",
+        "LinkedCallback.cpp",
+    ],
+    target: {
+        recovery: {
+            exclude_srcs: [
+                "ChargerUtils.cpp",
+            ],
+        },
+    },
+}
+
+// AIDL version of android.hardware.health@2.1-service.
+// Default binder service of the health HAL.
+cc_binary {
+    name: "android.hardware.health-service.example",
+    relative_install_path: "hw",
+    init_rc: ["android.hardware.health-service.example.rc"],
+    vintf_fragments: ["android.hardware.health-service.example.xml"],
+    defaults: [
+        "libhealth_aidl_common_defaults",
+        "libhealth_aidl_charger_defaults",
+    ],
+    static_libs: [
+        "libhealth_aidl_impl",
+    ],
+    srcs: ["main.cpp"],
+    overrides: [
+        "charger",
+    ],
+}
diff --git a/health/aidl/default/ChargerUtils.cpp b/health/aidl/default/ChargerUtils.cpp
new file mode 100644
index 0000000..f8e208b
--- /dev/null
+++ b/health/aidl/default/ChargerUtils.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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-base/logging.h>
+#include <health-impl/ChargerUtils.h>
+
+namespace aidl::android::hardware::health::charger {
+
+std::optional<bool> ChargerCallback::ChargerShouldKeepScreenOn() {
+    return service_->ShouldKeepScreenOn();
+}
+
+bool ChargerCallback::ChargerIsOnline() {
+    auto hal_health_loop_sp = hal_health_loop_.lock();
+    if (hal_health_loop_sp == nullptr) {
+        // Assume no charger
+        return false;
+    }
+    return hal_health_loop_sp->charger_online();
+}
+
+void ChargerCallback::ChargerInitConfig(healthd_config* config) {
+    auto hal_health_loop_sp = hal_health_loop_.lock();
+    if (hal_health_loop_sp == nullptr) {
+        return;
+    }
+    return service_->OnInit(hal_health_loop_sp.get(), config);
+}
+
+int ChargerCallback::ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) {
+    auto hal_health_loop_sp = hal_health_loop_.lock();
+    if (hal_health_loop_sp == nullptr) {
+        return -1;
+    }
+    return hal_health_loop_sp->RegisterEvent(fd, func, wakeup);
+}
+
+void ChargerCallback::set_hal_health_loop(const std::weak_ptr<HalHealthLoop>& hal_health_loop) {
+    hal_health_loop_ = std::move(hal_health_loop);
+}
+
+// Implements HalHealthLoopCallback for AIDL charger
+// Adapter of (Charger, Health) ->  HalHealthLoopCallback
+class LoopCallback : public HalHealthLoopCallback {
+  public:
+    LoopCallback(const std::shared_ptr<Health>& service, ChargerCallback* charger_callback)
+        : service_(service), charger_(std::make_unique<::android::Charger>(charger_callback)) {}
+
+    void OnHeartbeat() override {
+        service_->OnHeartbeat();
+        charger_->OnHeartbeat();
+    }
+    // Return the minimum timeout. Negative values are treated as no values.
+    int OnPrepareToWait() override {
+        int timeout1 = service_->OnPrepareToWait();
+        int timeout2 = charger_->OnPrepareToWait();
+
+        if (timeout1 < 0) return timeout2;
+        if (timeout2 < 0) return timeout1;
+        return std::min(timeout1, timeout2);
+    }
+
+    void OnInit(HalHealthLoop*, struct healthd_config* config) override {
+        // Charger::OnInit calls ChargerInitConfig, which calls into the real Health::OnInit.
+        charger_->OnInit(config);
+    }
+
+    void OnHealthInfoChanged(const HealthInfo& health_info) override {
+        charger_->OnHealthInfoChanged(::android::ChargerHealthInfo{
+                .battery_level = health_info.batteryLevel,
+                .battery_status = health_info.batteryStatus,
+        });
+        service_->OnHealthInfoChanged(health_info);
+    }
+
+  private:
+    std::shared_ptr<Health> service_;
+    std::unique_ptr<::android::Charger> charger_;
+};
+
+int ChargerModeMain(const std::shared_ptr<Health>& binder,
+                    const std::shared_ptr<ChargerCallback>& charger_callback) {
+    LOG(INFO) << "Starting charger mode.";
+    //   parent stack ==========================================
+    //   current stack                                         ||
+    //    ||                                                   ||
+    //    V                                                    V
+    // hal_health_loop => loop_callback => charger --(raw)--> charger_callback
+    //    ^----------------(weak)---------------------------------'
+    auto loop_callback = std::make_shared<LoopCallback>(binder, charger_callback.get());
+    auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, std::move(loop_callback));
+    charger_callback->set_hal_health_loop(hal_health_loop);
+    return hal_health_loop->StartLoop();
+}
+
+}  // namespace aidl::android::hardware::health::charger
diff --git a/health/aidl/default/HalHealthLoop.cpp b/health/aidl/default/HalHealthLoop.cpp
new file mode 100644
index 0000000..c9a081e
--- /dev/null
+++ b/health/aidl/default/HalHealthLoop.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <health-impl/HalHealthLoop.h>
+
+#include <android-base/logging.h>
+
+#include <health-impl/Health.h>
+#include "health-convert.h"
+
+namespace aidl::android::hardware::health {
+
+// Unlike the HIDL version android::hardware::health::V2_1::implementation::HalHealthLoop,
+// do not define HalHealthLoop::Init because we no longer have Health::getHealthConfig.
+// Let the Health class handle Init.
+void HalHealthLoop::Init(struct healthd_config* config) {
+    callback_->OnInit(this, config);
+}
+
+void HalHealthLoop::Heartbeat() {
+    callback_->OnHeartbeat();
+}
+
+void HalHealthLoop::ScheduleBatteryUpdate() {
+    // ignore errors. impl may not be able to handle any callbacks, so
+    // update() may return errors.
+    if (auto res = service_->update(); !res.isOk()) {
+        LOG(WARNING) << "update() on the health HAL implementation failed with "
+                     << res.getDescription();
+    }
+
+    HealthInfo health_info;
+    auto res = service_->getHealthInfo(&health_info);
+    CHECK(res.isOk()) << "getHealthInfo() on the health HAL implementation failed with "
+                      << res.getDescription();
+    OnHealthInfoChanged(health_info);
+}
+
+int HalHealthLoop::PrepareToWait() {
+    return callback_->OnPrepareToWait();
+}
+
+void HalHealthLoop::OnHealthInfoChanged(const HealthInfo& health_info) {
+    callback_->OnHealthInfoChanged(health_info);
+    set_charger_online(health_info);
+    AdjustWakealarmPeriods(charger_online());
+}
+
+void HalHealthLoop::set_charger_online(const HealthInfo& health_info) {
+    charger_online_ = health_info.chargerAcOnline || health_info.chargerUsbOnline ||
+                      health_info.chargerWirelessOnline;
+}
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/Health.cpp b/health/aidl/default/Health.cpp
new file mode 100644
index 0000000..812e64a
--- /dev/null
+++ b/health/aidl/default/Health.cpp
@@ -0,0 +1,341 @@
+/*
+ * 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 "health-impl/Health.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <android/hardware/health/translate-ndk.h>
+#include <health/utils.h>
+
+#include "LinkedCallback.h"
+#include "health-convert.h"
+
+using std::string_literals::operator""s;
+
+namespace aidl::android::hardware::health {
+
+namespace {
+// Wrap LinkedCallback::OnCallbackDied() into a void(void*).
+void OnCallbackDiedWrapped(void* cookie) {
+    LinkedCallback* linked = reinterpret_cast<LinkedCallback*>(cookie);
+    linked->OnCallbackDied();
+}
+}  // namespace
+
+/*
+// If you need to call healthd_board_init, construct the Health instance with
+// the healthd_config after calling healthd_board_init:
+class MyHealth : public Health {
+  protected:
+    MyHealth() : Health(CreateConfig()) {}
+  private:
+    static std::unique_ptr<healthd_config> CreateConfig() {
+      auto config = std::make_unique<healthd_config>();
+      ::android::hardware::health::InitHealthdConfig(config.get());
+      healthd_board_init(config.get());
+      return std::move(config);
+    }
+};
+*/
+Health::Health(std::string_view instance_name, std::unique_ptr<struct healthd_config>&& config)
+    : instance_name_(instance_name),
+      healthd_config_(std::move(config)),
+      death_recipient_(AIBinder_DeathRecipient_new(&OnCallbackDiedWrapped)) {
+    battery_monitor_.init(healthd_config_.get());
+}
+
+//
+// Getters.
+//
+
+template <typename T>
+static ndk::ScopedAStatus GetProperty(::android::BatteryMonitor* monitor, int id, T defaultValue,
+                                      T* out) {
+    *out = defaultValue;
+    struct ::android::BatteryProperty prop;
+    ::android::status_t err = monitor->getProperty(static_cast<int>(id), &prop);
+    if (err == ::android::OK) {
+        *out = static_cast<T>(prop.valueInt64);
+    } else {
+        LOG(DEBUG) << "getProperty(" << id << ")"
+                   << " fails: (" << err << ") " << ::android::statusToString(err);
+    }
+
+    switch (err) {
+        case ::android::OK:
+            return ndk::ScopedAStatus::ok();
+        case ::android::NAME_NOT_FOUND:
+            return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+        default:
+            return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                    IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str());
+    }
+}
+
+ndk::ScopedAStatus Health::getChargeCounterUah(int32_t* out) {
+    return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CHARGE_COUNTER, 0, out);
+}
+
+ndk::ScopedAStatus Health::getCurrentNowMicroamps(int32_t* out) {
+    return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_NOW, 0, out);
+}
+
+ndk::ScopedAStatus Health::getCurrentAverageMicroamps(int32_t* out) {
+    return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_AVG, 0, out);
+}
+
+ndk::ScopedAStatus Health::getCapacity(int32_t* out) {
+    return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CAPACITY, 0, out);
+}
+
+ndk::ScopedAStatus Health::getEnergyCounterNwh(int64_t* out) {
+    return GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_ENERGY_COUNTER, 0, out);
+}
+
+ndk::ScopedAStatus Health::getChargeStatus(BatteryStatus* out) {
+    return GetProperty(&battery_monitor_, ::android::BATTERY_PROP_BATTERY_STATUS,
+                       BatteryStatus::UNKNOWN, out);
+}
+
+ndk::ScopedAStatus Health::getDiskStats(std::vector<DiskStats>*) {
+    // This implementation does not support DiskStats. An implementation may extend this
+    // class and override this function to support disk stats.
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Health::getStorageInfo(std::vector<StorageInfo>*) {
+    // This implementation does not support StorageInfo. An implementation may extend this
+    // class and override this function to support storage info.
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Health::getHealthInfo(HealthInfo* out) {
+    battery_monitor_.updateValues();
+
+    // TODO(b/177269435): BatteryMonitor should store AIDL HealthInfo instead.
+    auto health_info_2_1 = battery_monitor_.getHealthInfo_2_1();
+    if (!::android::h2a::translate(health_info_2_1, out)) {
+        return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                IHealth::STATUS_UNKNOWN, "Cannot translate HIDL HealthInfo to AIDL");
+    }
+
+    // Fill in storage infos; these aren't retrieved by BatteryMonitor.
+    if (auto res = getStorageInfo(&out->storageInfos); !res.isOk()) {
+        if (res.getServiceSpecificError() == 0 &&
+            res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) {
+            return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                    IHealth::STATUS_UNKNOWN,
+                    ("getStorageInfo fails: " + res.getDescription()).c_str());
+        }
+        LOG(DEBUG) << "getHealthInfo: getStorageInfo fails with service-specific error, clearing: "
+                   << res.getDescription();
+        out->storageInfos = {};
+    }
+    if (auto res = getDiskStats(&out->diskStats); !res.isOk()) {
+        if (res.getServiceSpecificError() == 0 &&
+            res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) {
+            return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                    IHealth::STATUS_UNKNOWN,
+                    ("getDiskStats fails: " + res.getDescription()).c_str());
+        }
+        LOG(DEBUG) << "getHealthInfo: getDiskStats fails with service-specific error, clearing: "
+                   << res.getDescription();
+        out->diskStats = {};
+    }
+
+    // A subclass may want to update health info struct before returning it.
+    UpdateHealthInfo(out);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t Health::dump(int fd, const char**, uint32_t) {
+    battery_monitor_.dumpState(fd);
+
+    ::android::base::WriteStringToFd("\ngetHealthInfo -> ", fd);
+    HealthInfo health_info;
+    auto res = getHealthInfo(&health_info);
+    if (res.isOk()) {
+        ::android::base::WriteStringToFd(health_info.toString(), fd);
+    } else {
+        ::android::base::WriteStringToFd(res.getDescription(), fd);
+    }
+
+    fsync(fd);
+    return STATUS_OK;
+}
+
+std::optional<bool> Health::ShouldKeepScreenOn() {
+    if (!healthd_config_->screen_on) {
+        return std::nullopt;
+    }
+
+    HealthInfo health_info;
+    auto res = getHealthInfo(&health_info);
+    if (!res.isOk()) {
+        return std::nullopt;
+    }
+
+    ::android::BatteryProperties props = {};
+    convert(health_info, &props);
+    return healthd_config_->screen_on(&props);
+}
+
+namespace {
+bool IsDeadObjectLogged(const ndk::ScopedAStatus& ret) {
+    if (ret.isOk()) return false;
+    if (ret.getStatus() == ::STATUS_DEAD_OBJECT) return true;
+    LOG(ERROR) << "Cannot call healthInfoChanged on callback: " << ret.getDescription();
+    return false;
+}
+}  // namespace
+
+//
+// Subclass helpers / overrides
+//
+
+void Health::UpdateHealthInfo(HealthInfo* /* health_info */) {
+    /*
+        // Sample code for a subclass to implement this:
+        // If you need to modify values (e.g. batteryChargeTimeToFullNowSeconds), do it here.
+        health_info->batteryChargeTimeToFullNowSeconds = calculate_charge_time_seconds();
+
+        // If you need to call healthd_board_battery_update, modify its signature
+        // and implementation to operate on HealthInfo directly, then call:
+        healthd_board_battery_update(health_info);
+    */
+}
+
+//
+// Methods that handle callbacks.
+//
+
+ndk::ScopedAStatus Health::registerCallback(const std::shared_ptr<IHealthInfoCallback>& callback) {
+    if (callback == nullptr) {
+        // For now, this shouldn't happen because argument is not nullable.
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    {
+        std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_);
+        callbacks_.emplace_back(LinkedCallback::Make(ref<Health>(), callback));
+        // unlock
+    }
+
+    HealthInfo health_info;
+    if (auto res = getHealthInfo(&health_info); !res.isOk()) {
+        LOG(WARNING) << "Cannot call getHealthInfo: " << res.getDescription();
+        // No health info to send, so return early.
+        return ndk::ScopedAStatus::ok();
+    }
+
+    if (auto res = callback->healthInfoChanged(health_info); IsDeadObjectLogged(res)) {
+        (void)unregisterCallback(callback);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Health::unregisterCallback(
+        const std::shared_ptr<IHealthInfoCallback>& callback) {
+    if (callback == nullptr) {
+        // For now, this shouldn't happen because argument is not nullable.
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_);
+
+    auto matches = [callback](const auto& linked) {
+        return linked->callback()->asBinder() == callback->asBinder();  // compares binder object
+    };
+    auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), matches);
+    bool removed = (it != callbacks_.end());
+    callbacks_.erase(it, callbacks_.end());  // calls unlinkToDeath on deleted callbacks.
+    return removed ? ndk::ScopedAStatus::ok()
+                   : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+// A combination of the HIDL version
+//   android::hardware::health::V2_1::implementation::Health::update() and
+//   android::hardware::health::V2_1::implementation::BinderHealth::update()
+ndk::ScopedAStatus Health::update() {
+    HealthInfo health_info;
+    if (auto res = getHealthInfo(&health_info); !res.isOk()) {
+        LOG(DEBUG) << "Cannot call getHealthInfo for update(): " << res.getDescription();
+        // Propagate service specific errors. If there's none, report unknown error.
+        if (res.getServiceSpecificError() != 0 ||
+            res.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+            return res;
+        }
+        return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                IHealth::STATUS_UNKNOWN, res.getDescription().c_str());
+    }
+    battery_monitor_.logValues();
+    OnHealthInfoChanged(health_info);
+    return ndk::ScopedAStatus::ok();
+}
+
+void Health::OnHealthInfoChanged(const HealthInfo& health_info) {
+    // Notify all callbacks
+    std::unique_lock<decltype(callbacks_lock_)> lock(callbacks_lock_);
+    // is_dead notifies a callback and return true if it is dead.
+    auto is_dead = [&](const auto& linked) {
+        auto res = linked->callback()->healthInfoChanged(health_info);
+        return IsDeadObjectLogged(res);
+    };
+    auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), is_dead);
+    callbacks_.erase(it, callbacks_.end());  // calls unlinkToDeath on deleted callbacks.
+    lock.unlock();
+
+    // Let HalHealthLoop::OnHealthInfoChanged() adjusts uevent / wakealarm periods
+}
+
+void Health::BinderEvent(uint32_t /*epevents*/) {
+    if (binder_fd_ >= 0) {
+        ABinderProcess_handlePolledCommands();
+    }
+}
+
+void Health::OnInit(HalHealthLoop* hal_health_loop, struct healthd_config* config) {
+    LOG(INFO) << instance_name_ << " instance initializing with healthd_config...";
+
+    // Similar to HIDL's android::hardware::health::V2_1::implementation::HalHealthLoop::Init,
+    // copy configuration parameters to |config| for HealthLoop (e.g. uevent / wake alarm periods)
+    *config = *healthd_config_.get();
+
+    binder_status_t status = ABinderProcess_setupPolling(&binder_fd_);
+
+    if (status == ::STATUS_OK && binder_fd_ >= 0) {
+        std::shared_ptr<Health> thiz = ref<Health>();
+        auto binder_event = [thiz](auto*, uint32_t epevents) { thiz->BinderEvent(epevents); };
+        if (hal_health_loop->RegisterEvent(binder_fd_, binder_event, EVENT_NO_WAKEUP_FD) != 0) {
+            PLOG(ERROR) << instance_name_ << " instance: Register for binder events failed";
+        }
+    }
+
+    std::string health_name = IHealth::descriptor + "/"s + instance_name_;
+    CHECK_EQ(STATUS_OK, AServiceManager_addService(this->asBinder().get(), health_name.c_str()))
+            << instance_name_ << ": Failed to register HAL";
+
+    LOG(INFO) << instance_name_ << ": Hal init done";
+}
+
+// Unlike hwbinder, for binder, there's no need to explicitly call flushCommands()
+// in PrepareToWait(). See b/139697085.
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/LinkedCallback.cpp b/health/aidl/default/LinkedCallback.cpp
new file mode 100644
index 0000000..2985ffe
--- /dev/null
+++ b/health/aidl/default/LinkedCallback.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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-base/logging.h>
+#include <android/binder_ibinder.h>
+
+#include <health-impl/Health.h>
+#include <utils/Errors.h>
+
+#include "LinkedCallback.h"
+
+namespace aidl::android::hardware::health {
+
+std::unique_ptr<LinkedCallback> LinkedCallback::Make(
+        std::shared_ptr<Health> service, std::shared_ptr<IHealthInfoCallback> callback) {
+    std::unique_ptr<LinkedCallback> ret(new LinkedCallback());
+    binder_status_t linkRet =
+            AIBinder_linkToDeath(callback->asBinder().get(), service->death_recipient_.get(),
+                                 reinterpret_cast<void*>(ret.get()));
+    if (linkRet != ::STATUS_OK) {
+        LOG(WARNING) << __func__ << "Cannot link to death: " << linkRet;
+        return nullptr;
+    }
+    ret->service_ = service;
+    ret->callback_ = std::move(callback);
+    return ret;
+}
+
+LinkedCallback::LinkedCallback() = default;
+
+LinkedCallback::~LinkedCallback() {
+    if (callback_ == nullptr) {
+        return;
+    }
+    auto status =
+            AIBinder_unlinkToDeath(callback_->asBinder().get(), service()->death_recipient_.get(),
+                                   reinterpret_cast<void*>(this));
+    if (status != STATUS_OK && status != STATUS_DEAD_OBJECT) {
+        LOG(WARNING) << __func__ << "Cannot unlink to death: " << ::android::statusToString(status);
+    }
+}
+
+std::shared_ptr<Health> LinkedCallback::service() {
+    auto service_sp = service_.lock();
+    CHECK_NE(nullptr, service_sp);
+    return service_sp;
+}
+
+void LinkedCallback::OnCallbackDied() {
+    service()->unregisterCallback(callback_);
+}
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/LinkedCallback.h b/health/aidl/default/LinkedCallback.h
new file mode 100644
index 0000000..82490a7
--- /dev/null
+++ b/health/aidl/default/LinkedCallback.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <memory>
+
+#include <aidl/android/hardware/health/IHealthInfoCallback.h>
+#include <android-base/macros.h>
+#include <android/binder_auto_utils.h>
+
+#include <health-impl/Health.h>
+
+namespace aidl::android::hardware::health {
+
+// Type of the cookie pointer in linkToDeath.
+// A (Health, IHealthInfoCallback) tuple.
+class LinkedCallback {
+  public:
+    // Automatically linkToDeath upon construction with the returned object as the cookie.
+    // service->death_reciepient() should be from CreateDeathRecipient().
+    // Not using a strong reference to |service| to avoid circular reference. The lifetime
+    // of |service| must be longer than this LinkedCallback object.
+    static std::unique_ptr<LinkedCallback> Make(std::shared_ptr<Health> service,
+                                                std::shared_ptr<IHealthInfoCallback> callback);
+
+    // Automatically unlinkToDeath upon destruction. So, it is always safe to reinterpret_cast
+    // the cookie back to the LinkedCallback object.
+    ~LinkedCallback();
+
+    // The wrapped IHealthInfoCallback object.
+    const std::shared_ptr<IHealthInfoCallback>& callback() const { return callback_; }
+
+    // On callback died, unreigster it from the service.
+    void OnCallbackDied();
+
+  private:
+    LinkedCallback();
+    DISALLOW_COPY_AND_ASSIGN(LinkedCallback);
+
+    std::shared_ptr<Health> service();
+
+    std::weak_ptr<Health> service_;
+    std::shared_ptr<IHealthInfoCallback> callback_;
+};
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/android.hardware.health-service.example.rc b/health/aidl/default/android.hardware.health-service.example.rc
new file mode 100644
index 0000000..dee3d11
--- /dev/null
+++ b/health/aidl/default/android.hardware.health-service.example.rc
@@ -0,0 +1,16 @@
+service vendor.health-default /vendor/bin/hw/android.hardware.health-service.example
+    class hal
+    user system
+    group system
+    capabilities WAKE_ALARM BLOCK_SUSPEND
+    file /dev/kmsg w
+
+service vendor.charger-default /vendor/bin/hw/android.hardware.health-service.example --charger
+    class charger
+    user system
+    group system wakelock input
+    capabilities SYS_BOOT
+    file /dev/kmsg w
+    file /sys/fs/pstore/console-ramoops-0 r
+    file /sys/fs/pstore/console-ramoops r
+    file /proc/last_kmsg r
diff --git a/health/aidl/default/android.hardware.health-service.example.xml b/health/aidl/default/android.hardware.health-service.example.xml
new file mode 100644
index 0000000..98026cb
--- /dev/null
+++ b/health/aidl/default/android.hardware.health-service.example.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.health</name>
+        <version>1</version>
+        <fqname>IHealth/default</fqname>
+    </hal>
+</manifest>
diff --git a/health/aidl/default/health-convert.cpp b/health/aidl/default/health-convert.cpp
new file mode 100644
index 0000000..b5251f4
--- /dev/null
+++ b/health/aidl/default/health-convert.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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 "health-convert.h"
+
+namespace aidl::android::hardware::health {
+
+void convert(const HealthInfo& info, struct ::android::BatteryProperties* p) {
+    p->chargerAcOnline = info.chargerAcOnline;
+    p->chargerUsbOnline = info.chargerUsbOnline;
+    p->chargerWirelessOnline = info.chargerWirelessOnline;
+    p->maxChargingCurrent = info.maxChargingCurrentMicroamps;
+    p->maxChargingVoltage = info.maxChargingVoltageMicrovolts;
+    p->batteryStatus = static_cast<int>(info.batteryStatus);
+    p->batteryHealth = static_cast<int>(info.batteryHealth);
+    p->batteryPresent = info.batteryPresent;
+    p->batteryLevel = info.batteryLevel;
+    p->batteryVoltage = info.batteryVoltageMillivolts;
+    p->batteryTemperature = info.batteryTemperatureTenthsCelsius;
+    p->batteryCurrent = info.batteryCurrentMicroamps;
+    p->batteryCycleCount = info.batteryCycleCount;
+    p->batteryFullCharge = info.batteryFullChargeUah;
+    p->batteryChargeCounter = info.batteryChargeCounterUah;
+    p->batteryTechnology = ::android::String8(info.batteryTechnology.c_str());
+}
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/health-convert.h b/health/aidl/default/health-convert.h
new file mode 100644
index 0000000..179ffc4
--- /dev/null
+++ b/health/aidl/default/health-convert.h
@@ -0,0 +1,31 @@
+/*
+ * 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 <aidl/android/hardware/health/HealthInfo.h>
+#include <batteryservice/BatteryService.h>
+#include <healthd/healthd.h>
+
+// Conversion between healthd types and AIDL health HAL types. Note that most
+// of the conversion loses information, because these types have a different
+// set of information.
+
+namespace aidl::android::hardware::health {
+
+void convert(const HealthInfo& info, struct ::android::BatteryProperties* out);
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/include/health-impl/ChargerUtils.h b/health/aidl/default/include/health-impl/ChargerUtils.h
new file mode 100644
index 0000000..8f94181
--- /dev/null
+++ b/health/aidl/default/include/health-impl/ChargerUtils.h
@@ -0,0 +1,51 @@
+/*
+ * 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 <optional>
+#include <type_traits>
+
+#include <android/binder_auto_utils.h>
+#include <charger/healthd_mode_charger.h>
+#include <health-impl/Health.h>
+
+#pragma once
+
+namespace aidl::android::hardware::health::charger {
+
+// Implements ChargerHalHealthLoopInterface for AIDL charger
+// Adapter of (Health, HalHealthLoop) -> ChargerHalHealthLoopInterface
+class ChargerCallback : public ::android::ChargerConfigurationInterface {
+  public:
+    ChargerCallback(const std::shared_ptr<Health>& service) : service_(service) {}
+    std::optional<bool> ChargerShouldKeepScreenOn() override;
+    bool ChargerIsOnline() override;
+    void ChargerInitConfig(healthd_config* config) override;
+    int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) override;
+
+    // Override to return true to replace `ro.charger.enable_suspend=true`
+    bool ChargerEnableSuspend() override { return false; }
+
+    void set_hal_health_loop(const std::weak_ptr<HalHealthLoop>& hal_health_loop);
+
+  private:
+    std::shared_ptr<Health> service_;
+    std::weak_ptr<HalHealthLoop> hal_health_loop_;
+};
+
+int ChargerModeMain(const std::shared_ptr<Health>& binder,
+                    const std::shared_ptr<ChargerCallback>& charger_callback);
+
+}  // namespace aidl::android::hardware::health::charger
diff --git a/health/aidl/default/include/health-impl/HalHealthLoop.h b/health/aidl/default/include/health-impl/HalHealthLoop.h
new file mode 100644
index 0000000..c46aaa4
--- /dev/null
+++ b/health/aidl/default/include/health-impl/HalHealthLoop.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <memory>
+#include <optional>
+
+#include <aidl/android/hardware/health/IHealth.h>
+#include <health/HealthLoop.h>
+
+namespace aidl::android::hardware::health {
+
+class HalHealthLoop;
+
+class HalHealthLoopCallback {
+  public:
+    virtual ~HalHealthLoopCallback() = default;
+
+    // Called by HalHealthLoop::Init
+    virtual void OnInit(HalHealthLoop* hal_health_loop, struct healthd_config* config) = 0;
+    // Called by HalHealthLoop::Heartbeat
+    virtual void OnHeartbeat(){};
+    // Called by HalHealthLoop::PrepareToWait
+    virtual int OnPrepareToWait() { return -1; }
+    // Called by HalHealthLoop::ScheduleBatteryUpdate
+    virtual void OnHealthInfoChanged(const HealthInfo&) {}
+};
+
+// AIDL version of android::hardware::health::V2_1::implementation::HalHealthLoop.
+// An implementation of HealthLoop for using a given health HAL.
+class HalHealthLoop final : public ::android::hardware::health::HealthLoop {
+  public:
+    // Caller must ensure that the lifetime of service_ is not shorter than this object.
+    HalHealthLoop(std::shared_ptr<IHealth> service, std::shared_ptr<HalHealthLoopCallback> callback)
+        : service_(std::move(service)), callback_(std::move(callback)) {}
+
+    using HealthLoop::RegisterEvent;
+
+    bool charger_online() const { return charger_online_; }
+
+  protected:
+    virtual void Init(struct healthd_config* config) override;
+    virtual void Heartbeat() override;
+    virtual int PrepareToWait() override;
+    virtual void ScheduleBatteryUpdate() override;
+
+  private:
+    std::shared_ptr<IHealth> service_;
+    std::shared_ptr<HalHealthLoopCallback> callback_;
+    bool charger_online_ = false;
+
+    // Helpers of OnHealthInfoChanged.
+    void set_charger_online(const HealthInfo& health_info);
+
+    // HealthLoop periodically calls ScheduleBatteryUpdate, which calls
+    // OnHealthInfoChanged callback. A subclass of the callback can override
+    // HalHealthLoopCallback::OnHealthInfoChanged to
+    // broadcast the health_info to interested listeners.
+    // This adjust uevents / wakealarm periods.
+    void OnHealthInfoChanged(const HealthInfo& health_info);
+};
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/include/health-impl/Health.h b/health/aidl/default/include/health-impl/Health.h
new file mode 100644
index 0000000..e49f44c
--- /dev/null
+++ b/health/aidl/default/include/health-impl/Health.h
@@ -0,0 +1,113 @@
+/*
+ * 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 <memory>
+#include <optional>
+
+#include <aidl/android/hardware/health/BnHealth.h>
+#include <aidl/android/hardware/health/IHealthInfoCallback.h>
+#include <android/binder_auto_utils.h>
+#include <health-impl/HalHealthLoop.h>
+#include <healthd/BatteryMonitor.h>
+#include <healthd/healthd.h>
+
+namespace aidl::android::hardware::health {
+
+class LinkedCallback;
+
+// AIDL version of android::hardware::health::V2_1::implementation::Health and BinderHealth.
+// There's no need to separate the two in AIDL because AIDL does not support passthrough transport.
+//
+// Instead of inheriting from HalHealthLoop directly, implements the callback interface to
+// HalHealthLoop instead.
+//
+// Sample implementation of health HAL.
+class Health : public BnHealth, public HalHealthLoopCallback {
+  public:
+    // Initialize with |config|.
+    // A subclass may modify |config| before passing it to the parent constructor.
+    // See implementation of Health for code samples.
+    Health(std::string_view instance_name, std::unique_ptr<struct healthd_config>&& config);
+
+    ndk::ScopedAStatus registerCallback(
+            const std::shared_ptr<IHealthInfoCallback>& callback) override;
+    ndk::ScopedAStatus unregisterCallback(
+            const std::shared_ptr<IHealthInfoCallback>& callback) override;
+    ndk::ScopedAStatus update() override;
+
+    // A subclass should not override this. Override UpdateHealthInfo instead.
+    ndk::ScopedAStatus getHealthInfo(HealthInfo* out) override final;
+
+    // A subclass is recommended to override the path in healthd_config in the constructor.
+    // Only override these if there are no suitable kernel interfaces to read these values.
+    ndk::ScopedAStatus getChargeCounterUah(int32_t* out) override;
+    ndk::ScopedAStatus getCurrentNowMicroamps(int32_t* out) override;
+    ndk::ScopedAStatus getCurrentAverageMicroamps(int32_t* out) override;
+    ndk::ScopedAStatus getCapacity(int32_t* out) override;
+    ndk::ScopedAStatus getChargeStatus(BatteryStatus* out) override;
+
+    // A subclass may either override these or provide function pointers in
+    // in healthd_config in the constructor.
+    // Prefer overriding these for efficiency.
+    ndk::ScopedAStatus getEnergyCounterNwh(int64_t* out) override;
+
+    // A subclass may override these for a specific device.
+    // The default implementations return nothing in |out|.
+    ndk::ScopedAStatus getDiskStats(std::vector<DiskStats>* out) override;
+    ndk::ScopedAStatus getStorageInfo(std::vector<StorageInfo>* out) override;
+
+    // A subclass may override these to provide a different implementation.
+    binder_status_t dump(int fd, const char** args, uint32_t num_args) override;
+
+    // HalHealthLoopCallback implementation.
+    void OnInit(HalHealthLoop* hal_health_loop, struct healthd_config* config) override;
+    void OnHealthInfoChanged(const HealthInfo& health_info) override;
+
+    // A subclass may override this if it wants to handle binder events differently.
+    virtual void BinderEvent(uint32_t epevents);
+
+    // A subclass may override this to determine whether screen should be kept on in charger mode.
+    // Default is to invoke healthd_config->screen_on() on the BatteryProperties converted
+    // from getHealthInfo.
+    // Prefer overriding these to providing screen_on in healthd_config in the constructor
+    // for efficiency.
+    virtual std::optional<bool> ShouldKeepScreenOn();
+
+  protected:
+    // A subclass can override this to modify any health info object before
+    // returning to clients. This is similar to healthd_board_battery_update().
+    // By default, it does nothing.
+    // See implementation of Health for code samples.
+    virtual void UpdateHealthInfo(HealthInfo* health_info);
+
+  private:
+    friend LinkedCallback;  // for exposing death_recipient_
+
+    bool unregisterCallbackInternal(std::shared_ptr<IHealthInfoCallback> callback);
+
+    std::string instance_name_;
+    ::android::BatteryMonitor battery_monitor_;
+    std::unique_ptr<struct healthd_config> healthd_config_;
+
+    ndk::ScopedAIBinder_DeathRecipient death_recipient_;
+    int binder_fd_ = -1;
+    std::mutex callbacks_lock_;
+    std::vector<std::unique_ptr<LinkedCallback>> callbacks_;
+};
+
+}  // namespace aidl::android::hardware::health
diff --git a/health/aidl/default/main.cpp b/health/aidl/default/main.cpp
new file mode 100644
index 0000000..76c6ba0
--- /dev/null
+++ b/health/aidl/default/main.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <health-impl/Health.h>
+#include <health/utils.h>
+
+#ifndef CHARGER_FORCE_NO_UI
+#define CHARGER_FORCE_NO_UI 0
+#endif
+
+#if !CHARGER_FORCE_NO_UI
+#include <health-impl/ChargerUtils.h>
+#endif
+
+using aidl::android::hardware::health::HalHealthLoop;
+using aidl::android::hardware::health::Health;
+
+#if !CHARGER_FORCE_NO_UI
+using aidl::android::hardware::health::charger::ChargerCallback;
+using aidl::android::hardware::health::charger::ChargerModeMain;
+#endif
+
+static constexpr const char* gInstanceName = "default";
+static constexpr std::string_view gChargerArg{"--charger"};
+
+int main(int argc, char** argv) {
+    // make a default health service
+    auto config = std::make_unique<healthd_config>();
+    ::android::hardware::health::InitHealthdConfig(config.get());
+    auto binder = ndk::SharedRefBase::make<Health>(gInstanceName, std::move(config));
+
+    if (argc >= 2 && argv[1] == gChargerArg) {
+        android::base::InitLogging(argv, &android::base::KernelLogger);
+
+#if !CHARGER_FORCE_NO_UI
+        // If charger shouldn't have UI for your device, simply drop the line below
+        // for your service implementation. This corresponds to
+        // ro.charger.no_ui=true
+        return ChargerModeMain(binder, std::make_shared<ChargerCallback>(binder));
+#endif
+
+        LOG(INFO) << "Starting charger mode without UI.";
+    } else {
+        LOG(INFO) << "Starting health HAL.";
+    }
+
+    auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
+    return hal_health_loop->StartLoop();
+}
diff --git a/health/aidl/include/android/hardware/health/translate-ndk.h b/health/aidl/include/android/hardware/health/translate-ndk.h
new file mode 100644
index 0000000..2f8fe04
--- /dev/null
+++ b/health/aidl/include/android/hardware/health/translate-ndk.h
@@ -0,0 +1,39 @@
+/*
+ * 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 <aidl/android/hardware/health/BatteryCapacityLevel.h>
+#include <aidl/android/hardware/health/DiskStats.h>
+#include <aidl/android/hardware/health/HealthInfo.h>
+#include <aidl/android/hardware/health/StorageInfo.h>
+#include <android/hardware/health/2.0/types.h>
+#include <android/hardware/health/2.1/types.h>
+#include <limits>
+
+namespace android::h2a {
+
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_0::StorageInfo& in,
+        aidl::android::hardware::health::StorageInfo* out);
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_0::DiskStats& in,
+        aidl::android::hardware::health::DiskStats* out);
+__attribute__((warn_unused_result)) bool translate(
+        const ::android::hardware::health::V2_1::HealthInfo& in,
+        aidl::android::hardware::health::HealthInfo* out);
+
+}  // namespace android::h2a
diff --git a/health/aidl/vts/functional/Android.bp b/health/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..434f565
--- /dev/null
+++ b/health/aidl/vts/functional/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+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: "VtsHalHealthTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsHalHealthTargetTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "android.hardware.health-V1-ndk",
+        "libgmock",
+    ],
+    header_libs: [
+        "libhealthtest_headers",
+    ],
+    test_suites: [
+        "vts",
+    ],
+}
diff --git a/health/aidl/vts/functional/VtsHalHealthTargetTest.cpp b/health/aidl/vts/functional/VtsHalHealthTargetTest.cpp
new file mode 100644
index 0000000..3e07188
--- /dev/null
+++ b/health/aidl/vts/functional/VtsHalHealthTargetTest.cpp
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2020 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 "health_aidl_hal_test"
+
+#include <chrono>
+#include <memory>
+#include <thread>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/health/BnHealthInfoCallback.h>
+#include <aidl/android/hardware/health/IHealth.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_enums.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <health-test/TestUtils.h>
+
+using android::getAidlHalInstanceNames;
+using android::PrintInstanceNameToString;
+using android::hardware::health::test_utils::SucceedOnce;
+using ndk::enum_range;
+using ndk::ScopedAStatus;
+using ndk::SharedRefBase;
+using ndk::SpAIBinder;
+using testing::AllOf;
+using testing::AnyOf;
+using testing::AnyOfArray;
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+using testing::Contains;
+using testing::Each;
+using testing::Eq;
+using testing::ExplainMatchResult;
+using testing::Ge;
+using testing::Gt;
+using testing::Le;
+using testing::Lt;
+using testing::Matcher;
+using testing::Not;
+using namespace std::string_literals;
+using namespace std::chrono_literals;
+
+namespace aidl::android::hardware::health {
+
+static constexpr int32_t kFullChargeDesignCapMinUah = 100 * 1000;
+static constexpr int32_t kFullChargeDesignCapMaxUah = 100 * 1000 * 1000;
+
+MATCHER(IsOk, "") {
+    *result_listener << "status is " << arg.getDescription();
+    return arg.isOk();
+}
+
+MATCHER_P(ExceptionIs, exception_code, "") {
+    *result_listener << "status is " << arg.getDescription();
+    return arg.getExceptionCode() == exception_code;
+}
+
+template <typename T>
+Matcher<T> InClosedRange(const T& lo, const T& hi) {
+    return AllOf(Ge(lo), Le(hi));
+}
+
+template <typename T>
+Matcher<T> IsValidEnum() {
+    return AnyOfArray(enum_range<T>().begin(), enum_range<T>().end());
+}
+
+class HealthAidl : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override {
+        SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+        health = IHealth::fromBinder(binder);
+        ASSERT_NE(health, nullptr);
+    }
+    std::shared_ptr<IHealth> health;
+};
+
+class Callback : public BnHealthInfoCallback {
+  public:
+    ScopedAStatus healthInfoChanged(const HealthInfo&) override {
+        {
+            std::lock_guard<std::mutex> lock(mutex_);
+            invoked_ = true;
+        }
+        invoked_notify_.notify_all();
+        return ScopedAStatus::ok();
+    }
+    template <typename R, typename P>
+    [[nodiscard]] bool waitInvoke(std::chrono::duration<R, P> duration) {
+        std::unique_lock<std::mutex> lock(mutex_);
+        bool r = invoked_notify_.wait_for(lock, duration, [this] { return this->invoked_; });
+        invoked_ = false;
+        return r;
+    }
+
+  private:
+    std::mutex mutex_;
+    std::condition_variable invoked_notify_;
+    bool invoked_ = false;
+};
+
+TEST_P(HealthAidl, Callbacks) {
+    auto first_callback = SharedRefBase::make<Callback>();
+    auto second_callback = SharedRefBase::make<Callback>();
+
+    ASSERT_THAT(health->registerCallback(first_callback), IsOk());
+    ASSERT_THAT(health->registerCallback(second_callback), IsOk());
+
+    // registerCallback may or may not invoke the callback immediately, so the test needs
+    // to wait for the invocation. If the implementation chooses not to invoke the callback
+    // immediately, just wait for some time.
+    (void)first_callback->waitInvoke(200ms);
+    (void)second_callback->waitInvoke(200ms);
+
+    // assert that the first callback is invoked when update is called.
+    ASSERT_THAT(health->update(), IsOk());
+
+    ASSERT_TRUE(first_callback->waitInvoke(1s));
+    ASSERT_TRUE(second_callback->waitInvoke(1s));
+
+    ASSERT_THAT(health->unregisterCallback(first_callback), IsOk());
+
+    // clear any potentially pending callbacks result from wakealarm / kernel events
+    // If there is none, just wait for some time.
+    (void)first_callback->waitInvoke(200ms);
+    (void)second_callback->waitInvoke(200ms);
+
+    // assert that the second callback is still invoked even though the first is unregistered.
+    ASSERT_THAT(health->update(), IsOk());
+
+    ASSERT_FALSE(first_callback->waitInvoke(200ms));
+    ASSERT_TRUE(second_callback->waitInvoke(1s));
+
+    ASSERT_THAT(health->unregisterCallback(second_callback), IsOk());
+}
+
+TEST_P(HealthAidl, UnregisterNonExistentCallback) {
+    auto callback = SharedRefBase::make<Callback>();
+    auto ret = health->unregisterCallback(callback);
+    ASSERT_THAT(ret, ExceptionIs(EX_ILLEGAL_ARGUMENT));
+}
+
+/*
+ * Tests the values returned by getChargeCounterUah() from interface IHealth.
+ */
+TEST_P(HealthAidl, getChargeCounterUah) {
+    int32_t value;
+    auto status = health->getChargeCounterUah(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, Ge(0));
+}
+
+/*
+ * Tests the values returned by getCurrentNowMicroamps() from interface IHealth.
+ */
+TEST_P(HealthAidl, getCurrentNowMicroamps) {
+    int32_t value;
+    auto status = health->getCurrentNowMicroamps(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, Not(INT32_MIN));
+}
+
+/*
+ * Tests the values returned by getCurrentAverageMicroamps() from interface IHealth.
+ */
+TEST_P(HealthAidl, getCurrentAverageMicroamps) {
+    int32_t value;
+    auto status = health->getCurrentAverageMicroamps(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, Not(INT32_MIN));
+}
+
+/*
+ * Tests the values returned by getCapacity() from interface IHealth.
+ */
+TEST_P(HealthAidl, getCapacity) {
+    int32_t value;
+    auto status = health->getCapacity(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, InClosedRange(0, 100));
+}
+
+/*
+ * Tests the values returned by getEnergyCounterNwh() from interface IHealth.
+ */
+TEST_P(HealthAidl, getEnergyCounterNwh) {
+    int64_t value;
+    auto status = health->getEnergyCounterNwh(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, Not(INT64_MIN));
+}
+
+/*
+ * Tests the values returned by getChargeStatus() from interface IHealth.
+ */
+TEST_P(HealthAidl, getChargeStatus) {
+    BatteryStatus value;
+    auto status = health->getChargeStatus(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, IsValidEnum<BatteryStatus>());
+}
+
+MATCHER(IsValidStorageInfo, "") {
+    *result_listener << "value is " << arg.toString() << ".";
+    if (!ExplainMatchResult(InClosedRange(0, 3), arg.eol, result_listener)) {
+        *result_listener << " for eol.";
+        return false;
+    }
+    if (!ExplainMatchResult(InClosedRange(0, 0x0B), arg.lifetimeA, result_listener)) {
+        *result_listener << " for lifetimeA.";
+        return false;
+    }
+    if (!ExplainMatchResult(InClosedRange(0, 0x0B), arg.lifetimeB, result_listener)) {
+        *result_listener << " for lifetimeB.";
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Tests the values returned by getStorageInfo() from interface IHealth.
+ */
+TEST_P(HealthAidl, getStorageInfo) {
+    std::vector<StorageInfo> value;
+    auto status = health->getStorageInfo(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, Each(IsValidStorageInfo()));
+}
+
+/*
+ * Tests the values returned by getDiskStats() from interface IHealth.
+ */
+TEST_P(HealthAidl, getDiskStats) {
+    std::vector<DiskStats> value;
+    auto status = health->getDiskStats(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+}
+
+MATCHER(IsValidHealthInfo, "") {
+    *result_listener << "value is " << arg.toString() << ".";
+    if (!ExplainMatchResult(Each(IsValidStorageInfo()), arg.storageInfos, result_listener)) {
+        *result_listener << " for storageInfos.";
+        return false;
+    }
+
+    if (!ExplainMatchResult(Not(INT32_MIN), arg.batteryCurrentMicroamps, result_listener)) {
+        *result_listener << " for batteryCurrentMicroamps.";
+        return false;
+    }
+
+    if (!ExplainMatchResult(InClosedRange(0, 100), arg.batteryLevel, result_listener)) {
+        *result_listener << " for batteryLevel.";
+        return false;
+    }
+
+    if (!ExplainMatchResult(IsValidEnum<BatteryHealth>(), arg.batteryHealth, result_listener)) {
+        *result_listener << " for batteryHealth.";
+        return false;
+    }
+
+    if (!ExplainMatchResult(IsValidEnum<BatteryStatus>(), arg.batteryStatus, result_listener)) {
+        *result_listener << " for batteryStatus.";
+        return false;
+    }
+
+    if (arg.batteryPresent) {
+        if (!ExplainMatchResult(Gt(0), arg.batteryChargeCounterUah, result_listener)) {
+            *result_listener << " for batteryChargeCounterUah when battery is present.";
+            return false;
+        }
+        if (!ExplainMatchResult(Not(BatteryStatus::UNKNOWN), arg.batteryStatus, result_listener)) {
+            *result_listener << " for batteryStatus when battery is present.";
+            return false;
+        }
+    }
+
+    if (!ExplainMatchResult(IsValidEnum<BatteryCapacityLevel>(), arg.batteryCapacityLevel,
+                            result_listener)) {
+        *result_listener << " for batteryCapacityLevel.";
+        return false;
+    }
+    if (!ExplainMatchResult(Ge(-1), arg.batteryChargeTimeToFullNowSeconds, result_listener)) {
+        *result_listener << " for batteryChargeTimeToFullNowSeconds.";
+        return false;
+    }
+
+    if (!ExplainMatchResult(
+                AnyOf(Eq(0), AllOf(Gt(kFullChargeDesignCapMinUah), Lt(kFullChargeDesignCapMaxUah))),
+                arg.batteryFullChargeDesignCapacityUah, result_listener)) {
+        *result_listener << " for batteryFullChargeDesignCapacityUah. It should be greater than "
+                            "100 mAh and less than 100,000 mAh, or 0 if unknown";
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Tests the values returned by getHealthInfo() from interface IHealth.
+ */
+TEST_P(HealthAidl, getHealthInfo) {
+    HealthInfo value;
+    auto status = health->getHealthInfo(&value);
+    ASSERT_THAT(status, AnyOf(IsOk(), ExceptionIs(EX_UNSUPPORTED_OPERATION)));
+    if (!status.isOk()) return;
+    ASSERT_THAT(value, IsValidHealthInfo());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HealthAidl);
+INSTANTIATE_TEST_SUITE_P(Health, HealthAidl,
+                         testing::ValuesIn(getAidlHalInstanceNames(IHealth::descriptor)),
+                         PrintInstanceNameToString);
+
+// For battery current tests, value may not be stable if the battery current has fluctuated.
+// Retry in a bit more time (with the following timeout) and consider the test successful if it
+// has succeed once.
+static constexpr auto gBatteryTestTimeout = 1min;
+static constexpr double gCurrentCompareFactor = 0.50;
+class BatteryTest : public HealthAidl {};
+
+// Tuple for all IHealth::get* API return values.
+template <typename T>
+struct HalResult {
+    std::shared_ptr<ScopedAStatus> result = std::make_shared<ScopedAStatus>();
+    T value;
+};
+
+// Needs to be called repeatedly within a period of time to ensure values are initialized.
+static AssertionResult IsBatteryCurrentSignCorrect(const HalResult<BatteryStatus>& status,
+                                                   const HalResult<int32_t>& current,
+                                                   bool acceptZeroCurrentAsUnknown) {
+    // getChargeStatus / getCurrentNow / getCurrentAverage / getHealthInfo already tested above.
+    // Here, just skip if not ok.
+    if (!status.result->isOk()) {
+        return AssertionSuccess() << "getChargeStatus / getHealthInfo returned "
+                                  << status.result->getDescription() << ", skipping";
+    }
+
+    if (!current.result->isOk()) {
+        return AssertionSuccess() << "getCurrentNow / getCurrentAverage returned "
+                                  << current.result->getDescription() << ", skipping";
+    }
+
+    return ::android::hardware::health::test_utils::IsBatteryCurrentSignCorrect(
+            status.value, current.value, acceptZeroCurrentAsUnknown,
+            [](BatteryStatus status) { return toString(status); });
+}
+
+static AssertionResult IsBatteryCurrentSimilar(const HalResult<BatteryStatus>& status,
+                                               const HalResult<int32_t>& current_now,
+                                               const HalResult<int32_t>& current_average) {
+    if (status.result->isOk() && status.value == BatteryStatus::FULL) {
+        // No reason to test on full battery because battery current load fluctuates.
+        return AssertionSuccess() << "Battery is full, skipping";
+    }
+
+    // getCurrentNow / getCurrentAverage / getHealthInfo already tested above. Here, just skip if
+    // not SUCCESS or value 0.
+    if (!current_now.result->isOk() || current_now.value == 0) {
+        return AssertionSuccess() << "getCurrentNow returned "
+                                  << current_now.result->getDescription() << " with value "
+                                  << current_now.value << ", skipping";
+    }
+
+    if (!current_average.result->isOk() || current_average.value == 0) {
+        return AssertionSuccess() << "getCurrentAverage returned "
+                                  << current_average.result->getDescription() << " with value "
+                                  << current_average.value << ", skipping";
+    }
+
+    return ::android::hardware::health::test_utils::IsBatteryCurrentSimilar(
+            current_now.value, current_average.value, gCurrentCompareFactor);
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstChargeStatusInHealthInfo) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<HealthInfo> health_info;
+        *health_info.result = health->getHealthInfo(&health_info.value);
+
+        return IsBatteryCurrentSignCorrect(
+                {health_info.result, health_info.value.batteryStatus},
+                {health_info.result, health_info.value.batteryCurrentMicroamps},
+                true /* accept zero current as unknown */);
+    };
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_now becomes stable.";
+}
+
+TEST_P(BatteryTest, AverageCurrentAgainstChargeStatusInHealthInfo) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<HealthInfo> health_info;
+        *health_info.result = health->getHealthInfo(&health_info.value);
+        return IsBatteryCurrentSignCorrect(
+                {health_info.result, health_info.value.batteryStatus},
+                {health_info.result, health_info.value.batteryCurrentAverageMicroamps},
+                true /* accept zero current as unknown */);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_average becomes stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstAverageCurrentInHealthInfo) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<HealthInfo> health_info;
+        *health_info.result = health->getHealthInfo(&health_info.value);
+        return IsBatteryCurrentSimilar(
+                {health_info.result, health_info.value.batteryStatus},
+                {health_info.result, health_info.value.batteryCurrentMicroamps},
+                {health_info.result, health_info.value.batteryCurrentAverageMicroamps});
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_now and current_average becomes "
+               "stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstChargeStatusFromHal) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<BatteryStatus> status;
+        *status.result = health->getChargeStatus(&status.value);
+        HalResult<int32_t> current_now;
+        *current_now.result = health->getCurrentNowMicroamps(&current_now.value);
+        return IsBatteryCurrentSignCorrect(status, current_now,
+                                           false /* accept zero current as unknown */);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_now becomes stable.";
+}
+
+TEST_P(BatteryTest, AverageCurrentAgainstChargeStatusFromHal) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<BatteryStatus> status;
+        *status.result = health->getChargeStatus(&status.value);
+        HalResult<int32_t> current_average;
+        *current_average.result = health->getCurrentAverageMicroamps(&current_average.value);
+        return IsBatteryCurrentSignCorrect(status, current_average,
+                                           false /* accept zero current as unknown */);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_average becomes stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstAverageCurrentFromHal) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<BatteryStatus> status;
+        *status.result = health->getChargeStatus(&status.value);
+        HalResult<int32_t> current_now;
+        *current_now.result = health->getCurrentNowMicroamps(&current_now.value);
+        HalResult<int32_t> current_average;
+        *current_average.result = health->getCurrentAverageMicroamps(&current_average.value);
+        return IsBatteryCurrentSimilar(status, current_now, current_average);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when current_average becomes stable.";
+}
+
+AssertionResult IsBatteryStatusCorrect(const HalResult<BatteryStatus>& status,
+                                       const HalResult<HealthInfo>& health_info) {
+    // getChargetStatus / getHealthInfo is already tested above. Here, just skip if not ok.
+    if (!health_info.result->isOk()) {
+        return AssertionSuccess() << "getHealthInfo returned "
+                                  << health_info.result->getDescription() << ", skipping";
+    }
+    if (!status.result->isOk()) {
+        return AssertionSuccess() << "getChargeStatus returned " << status.result->getDescription()
+                                  << ", skipping";
+    }
+    return ::android::hardware::health::test_utils::IsBatteryStatusCorrect(
+            status.value, health_info.value, [](BatteryStatus status) { return toString(status); });
+}
+
+TEST_P(BatteryTest, ConnectedAgainstStatusFromHal) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<BatteryStatus> status;
+        *status.result = health->getChargeStatus(&status.value);
+        HalResult<HealthInfo> health_info;
+        *health_info.result = health->getHealthInfo(&health_info.value);
+        return IsBatteryStatusCorrect(status, health_info);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when battery_status becomes stable.";
+}
+
+TEST_P(BatteryTest, ConnectedAgainstStatusInHealthInfo) {
+    auto testOnce = [&]() -> AssertionResult {
+        HalResult<HealthInfo> health_info;
+        *health_info.result = health->getHealthInfo(&health_info.value);
+        return IsBatteryStatusCorrect({health_info.result, health_info.value.batteryStatus},
+                                      health_info);
+    };
+
+    EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+            << "You may want to try again later when getHealthInfo becomes stable.";
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BatteryTest);
+INSTANTIATE_TEST_SUITE_P(Health, BatteryTest,
+                         testing::ValuesIn(getAidlHalInstanceNames(IHealth::descriptor)),
+                         PrintInstanceNameToString);
+
+}  // namespace aidl::android::hardware::health
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/health/utils/libhealthloop/include/health/HealthLoop.h b/health/utils/libhealthloop/include/health/HealthLoop.h
index 693e6cb..54b2740 100644
--- a/health/utils/libhealthloop/include/health/HealthLoop.h
+++ b/health/utils/libhealthloop/include/health/HealthLoop.h
@@ -46,6 +46,12 @@
     // Init is called right after epollfd_ is initialized (so RegisterEvent
     // is allowed) but before other things are initialized (so SetChargerOnline
     // is not allowed.)
+    // The implementation of Init() should pull configuration from the
+    // underlying health HAL (via getHealthConfig()), and store it into
+    // |config|. The implementation may not initialize:
+    // - screen_on, because charger calls getScreenOn() from the HAL directly
+    // - ignorePowerSupplyNames, because it isn't used by any clients of the
+    // health HAL.
     virtual void Init(healthd_config* config) = 0;
     virtual void Heartbeat() = 0;
     virtual int PrepareToWait() = 0;
diff --git a/health/utils/libhealthtest/Android.bp b/health/utils/libhealthtest/Android.bp
new file mode 100644
index 0000000..0993cb6
--- /dev/null
+++ b/health/utils/libhealthtest/Android.bp
@@ -0,0 +1,38 @@
+// 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.
+
+// Utils library for VTS tests.
+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_headers {
+    name: "libhealthtest_headers",
+    static_libs: [
+        "libgmock",
+        "libgtest",
+    ],
+    export_static_lib_headers: [
+        "libgmock",
+        "libgtest",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+}
diff --git a/health/utils/libhealthtest/include/health-test/TestUtils.h b/health/utils/libhealthtest/include/health-test/TestUtils.h
new file mode 100644
index 0000000..e69c411
--- /dev/null
+++ b/health/utils/libhealthtest/include/health-test/TestUtils.h
@@ -0,0 +1,149 @@
+#pragma once
+
+#include <chrono>
+
+#include <gtest/gtest.h>
+
+namespace android::hardware::health::test_utils {
+
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+using std::chrono_literals::operator""s;
+
+// Needs to be called repeatedly within a period of time to ensure values are initialized.
+template <typename BatteryStatusType, typename BatteryStatusToStringFn>
+inline AssertionResult IsBatteryCurrentSignCorrect(const BatteryStatusType& status, int32_t current,
+                                                   bool acceptZeroCurrentAsUnknown,
+                                                   const BatteryStatusToStringFn& toString) {
+    // For IHealth.getCurrentNow/Average, if current is not available, it is expected that
+    // the error code is NOT_SUPPORTED, which is checked above. Hence, zero current is
+    // not treated as unknown values.
+    // For IHealth.getHealthInfo, if current is not available, health_info.current_* == 0.
+    // Caller of this function provides current.result == Result::SUCCESS. Hence, just skip the
+    // check.
+    if (current == 0 && acceptZeroCurrentAsUnknown) {
+        return AssertionSuccess()
+               << "current is 0, which indicates the value may not be available. Skipping.";
+    }
+
+    switch (status) {
+        case BatteryStatusType::UNKNOWN:
+            if (current != 0) {
+                // BatteryStatus may be UNKNOWN initially with a non-zero current value, but
+                // after it is initialized, it should be known.
+                return AssertionFailure()
+                       << "BatteryStatus is UNKNOWN but current is not 0. Actual: " << current;
+            }
+            break;
+        case BatteryStatusType::CHARGING:
+            if (current <= 0) {
+                return AssertionFailure()
+                       << "BatteryStatus is CHARGING but current is not positive. Actual: "
+                       << current;
+            }
+            break;
+        case BatteryStatusType::NOT_CHARGING:
+            if (current > 0) {
+                return AssertionFailure() << "BatteryStatus is " << toString(status)
+                                          << " but current is positive. Actual: " << current;
+            }
+            break;
+        case BatteryStatusType::DISCHARGING:
+            if (current >= 0) {
+                return AssertionFailure() << "BatteryStatus is " << toString(status)
+                                          << " but current is not negative. Actual: " << current;
+            }
+            break;
+        case BatteryStatusType::FULL:
+            // Battery current may be positive or negative depending on the load.
+            break;
+        default:
+            return AssertionFailure() << "Unknown BatteryStatus " << toString(status);
+    }
+
+    return AssertionSuccess() << "BatteryStatus is " << toString(status)
+                              << " and current has the correct sign: " << current;
+}
+
+inline AssertionResult IsValueSimilar(int32_t dividend, int32_t divisor, double factor) {
+    auto difference = abs(dividend - divisor);
+    if (difference > factor * abs(divisor)) {
+        return AssertionFailure() << dividend << " and " << divisor
+                                  << " are not similar (factor = " << factor << ")";
+    }
+    return AssertionSuccess() << dividend << " and " << divisor
+                              << " are similar (factor = " << factor << ")";
+}
+
+inline AssertionResult IsBatteryCurrentSimilar(int32_t currentNow, int32_t currentAverage,
+                                               double currentCompareFactor) {
+    // Check that the two values are similar. Note that the two tests uses a different
+    // divisor to ensure that they are actually pretty similar. For example,
+    // IsValueSimilar(5,10,0.4) returns true, but IsValueSimlar(10,5,0.4) returns false.
+    auto res = IsValueSimilar(currentNow, currentAverage, currentCompareFactor)
+               << " for now vs. average. Check units.";
+    if (!res) return res;
+    res = IsValueSimilar(currentAverage, currentNow, currentCompareFactor)
+          << " for average vs. now. Check units.";
+    if (!res) return res;
+    return AssertionSuccess() << "currentNow = " << currentNow
+                              << " and currentAverage = " << currentAverage
+                              << " are considered similar.";
+}
+
+// Test that f() returns AssertionSuccess() once in a given period of time.
+template <typename Duration, typename Function>
+inline AssertionResult SucceedOnce(Duration d, Function f) {
+    AssertionResult result = AssertionFailure() << "Function is never evaluated.";
+    auto end = std::chrono::system_clock::now() + d;
+    while (std::chrono::system_clock::now() <= end) {
+        result = f();
+        if (result) {
+            return result;
+        }
+        std::this_thread::sleep_for(2s);
+    }
+    return result;
+}
+
+template <typename BatteryStatusType, typename BatteryInfoType, typename BatteryStatusToStringFn>
+AssertionResult IsBatteryStatusCorrect(const BatteryStatusType& status,
+                                       const BatteryInfoType& batteryInfo,
+                                       const BatteryStatusToStringFn& toString) {
+    bool isConnected = batteryInfo.chargerAcOnline || batteryInfo.chargerUsbOnline ||
+                       batteryInfo.chargerWirelessOnline;
+
+    std::stringstream message;
+    message << "BatteryStatus is " << toString(status) << " and " << (isConnected ? "" : "no ")
+            << "power source is connected: ac=" << batteryInfo.chargerAcOnline
+            << ", usb=" << batteryInfo.chargerUsbOnline
+            << ", wireless=" << batteryInfo.chargerWirelessOnline;
+
+    switch (status) {
+        case BatteryStatusType::UNKNOWN: {
+            // Don't enforce anything on isConnected on unknown battery status.
+            // Battery-less devices must report UNKNOWN battery status, but may report true
+            // or false on isConnected.
+        } break;
+        case BatteryStatusType::CHARGING:
+        case BatteryStatusType::NOT_CHARGING:
+        case BatteryStatusType::FULL: {
+            if (!isConnected) {
+                return AssertionFailure() << message.str();
+            }
+        } break;
+        case BatteryStatusType::DISCHARGING: {
+            if (isConnected) {
+                return AssertionFailure() << message.str();
+            }
+        } break;
+        default: {
+            return AssertionFailure() << "Unknown battery status value " << toString(status);
+        } break;
+    }
+
+    return AssertionSuccess() << message.str();
+}
+
+}  // namespace android::hardware::health::test_utils
diff --git a/neuralnetworks/1.0/utils/Android.bp b/neuralnetworks/1.0/utils/Android.bp
index 8c51c67..31cdded 100644
--- a/neuralnetworks/1.0/utils/Android.bp
+++ b/neuralnetworks/1.0/utils/Android.bp
@@ -31,6 +31,7 @@
     export_include_dirs: ["include"],
     cflags: ["-Wthread-safety"],
     static_libs: [
+        "libarect",
         "neuralnetworks_types",
         "neuralnetworks_utils_hal_common",
     ],
@@ -40,6 +41,11 @@
     export_static_lib_headers: [
         "neuralnetworks_utils_hal_common",
     ],
+    target: {
+        android: {
+            shared_libs: ["libnativewindow"],
+        },
+    },
 }
 
 cc_test {
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
index 3b32e1d..1ab9dcb 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Callbacks.h
@@ -24,9 +24,10 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
+#include "nnapi/hal/1.0/ProtectCallback.h"
+
 // See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
 // lifetimes across processes and for protecting asynchronous calls across HIDL.
 
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
index 5d4bdbc..a770d06 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Conversions.h
@@ -36,6 +36,7 @@
 GeneralResult<Operation> unvalidatedConvert(const hal::V1_0::Operation& operation);
 GeneralResult<Model::OperandValues> unvalidatedConvert(
         const hardware::hidl_vec<uint8_t>& operandValues);
+GeneralResult<SharedHandle> unvalidatedConvert(const hardware::hidl_handle& handle);
 GeneralResult<SharedMemory> unvalidatedConvert(const hardware::hidl_memory& memory);
 GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model);
 GeneralResult<Request::Argument> unvalidatedConvert(
@@ -65,6 +66,7 @@
 nn::GeneralResult<Operation> unvalidatedConvert(const nn::Operation& operation);
 nn::GeneralResult<hidl_vec<uint8_t>> unvalidatedConvert(
         const nn::Model::OperandValues& operandValues);
+nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle);
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory);
 nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model);
 nn::GeneralResult<RequestArgument> unvalidatedConvert(const nn::Request::Argument& requestArgument);
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
index db3b2ad..0a6ca3e 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Device.h
@@ -24,7 +24,8 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
+
+#include "nnapi/hal/1.0/ProtectCallback.h"
 
 #include <functional>
 #include <memory>
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Execution.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Execution.h
index e201e25..66497c2 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Execution.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Execution.h
@@ -22,9 +22,9 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include "PreparedModel.h"
+#include "nnapi/hal/1.0/ProtectCallback.h"
 
 #include <memory>
 #include <utility>
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/HandleError.h
similarity index 79%
rename from neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
rename to neuralnetworks/1.0/utils/include/nnapi/hal/1.0/HandleError.h
index e51f916..8e02633 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/HandleError.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/HandleError.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
-#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_HANDLE_ERROR_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_HANDLE_ERROR_H
 
 #include <android/hidl/base/1.0/IBase.h>
 #include <hidl/HidlSupport.h>
@@ -27,7 +27,7 @@
 namespace android::hardware::neuralnetworks::utils {
 
 template <typename Type>
-nn::GeneralResult<Type> handleTransportError(const hardware::Return<Type>& ret) {
+nn::GeneralResult<Type> handleTransportError(const Return<Type>& ret) {
     if (ret.isDeadObject()) {
         return nn::error(nn::ErrorStatus::DEAD_OBJECT)
                << "Return<>::isDeadObject returned true: " << ret.description();
@@ -52,13 +52,13 @@
         std::move(result).value();                                                           \
     })
 
-#define HANDLE_HAL_STATUS(status)                                       \
-    if (const auto canonical = ::android::nn::convert(status).value_or( \
-                ::android::nn::ErrorStatus::GENERAL_FAILURE);           \
-        canonical == ::android::nn::ErrorStatus::NONE) {                \
-    } else                                                              \
+#define HANDLE_STATUS_HIDL(status)                                                            \
+    if (const ::android::nn::ErrorStatus canonical = ::android::nn::convert(status).value_or( \
+                ::android::nn::ErrorStatus::GENERAL_FAILURE);                                 \
+        canonical == ::android::nn::ErrorStatus::NONE) {                                      \
+    } else                                                                                    \
         return NN_ERROR(canonical)
 
 }  // namespace android::hardware::neuralnetworks::utils
 
-#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_HANDLE_ERROR_H
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_HANDLE_ERROR_H
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
index 48be595..bdb5b54 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/PreparedModel.h
@@ -22,7 +22,8 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
+
+#include "nnapi/hal/1.0/ProtectCallback.h"
 
 #include <memory>
 #include <tuple>
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/ProtectCallback.h
similarity index 93%
rename from neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h
rename to neuralnetworks/1.0/utils/include/nnapi/hal/1.0/ProtectCallback.h
index 05110bc..7418cfa 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/ProtectCallback.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/ProtectCallback.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_PROTECT_CALLBACK_H
-#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_PROTECT_CALLBACK_H
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_PROTECT_CALLBACK_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_PROTECT_CALLBACK_H
 
 #include <android-base/scopeguard.h>
 #include <android-base/thread_annotations.h>
@@ -98,4 +98,4 @@
 
 }  // namespace android::hardware::neuralnetworks::utils
 
-#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_PROTECT_CALLBACK_H
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_0_UTILS_PROTECT_CALLBACK_H
diff --git a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
index 360b338..5c1480e 100644
--- a/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
+++ b/neuralnetworks/1.0/utils/include/nnapi/hal/1.0/Utils.h
@@ -25,7 +25,6 @@
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
-#include <nnapi/hal/HandleError.h>
 
 namespace android::hardware::neuralnetworks::V1_0::utils {
 
diff --git a/neuralnetworks/1.0/utils/src/Callbacks.cpp b/neuralnetworks/1.0/utils/src/Callbacks.cpp
index ea3ea56..7b478ae 100644
--- a/neuralnetworks/1.0/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.0/utils/src/Callbacks.cpp
@@ -17,7 +17,9 @@
 #include "Callbacks.h"
 
 #include "Conversions.h"
+#include "HandleError.h"
 #include "PreparedModel.h"
+#include "ProtectCallback.h"
 #include "Utils.h"
 
 #include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
@@ -27,8 +29,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <utility>
@@ -40,19 +40,19 @@
 
 nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
         ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
-    HANDLE_HAL_STATUS(status) << "get supported operations failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "get supported operations failed with " << toString(status);
     return supportedOperations;
 }
 
 nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
         ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
     return NN_TRY(PreparedModel::create(preparedModel));
 }
 
 nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> executionCallback(
         ErrorStatus status) {
-    HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
     return {};
 }
 
diff --git a/neuralnetworks/1.0/utils/src/Conversions.cpp b/neuralnetworks/1.0/utils/src/Conversions.cpp
index c0498eb..daa10fd 100644
--- a/neuralnetworks/1.0/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.0/utils/src/Conversions.cpp
@@ -37,6 +37,11 @@
 
 #include "Utils.h"
 
+#ifdef __ANDROID__
+#include <android/hardware_buffer.h>
+#include <vndk/hardware_buffer.h>
+#endif  // __ANDROID__
+
 namespace {
 
 template <typename Type>
@@ -49,6 +54,7 @@
 namespace android::nn {
 namespace {
 
+using hardware::hidl_handle;
 using hardware::hidl_memory;
 using hardware::hidl_vec;
 
@@ -74,6 +80,121 @@
     return canonical;
 }
 
+nn::GeneralResult<nn::Memory::Unknown::Handle> unknownHandleFromNativeHandle(
+        const native_handle_t* handle) {
+    if (handle == nullptr) {
+        return NN_ERROR() << "unknownHandleFromNativeHandle failed because handle is nullptr";
+    }
+
+    std::vector<base::unique_fd> fds =
+            NN_TRY(nn::dupFds(handle->data + 0, handle->data + handle->numFds));
+
+    std::vector<int> ints(handle->data + handle->numFds,
+                          handle->data + handle->numFds + handle->numInts);
+
+    return nn::Memory::Unknown::Handle{.fds = std::move(fds), .ints = std::move(ints)};
+}
+
+nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
+    CHECK_LE(memory.size(), std::numeric_limits<size_t>::max());
+    if (!memory.valid()) {
+        return NN_ERROR() << "Unable to convert invalid hidl_memory";
+    }
+
+    if (memory.name() == "ashmem") {
+        if (memory.handle()->numFds != 1) {
+            return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
+                              << memory.handle()->numFds << " numFds, but expected 1";
+        }
+        if (memory.handle()->numInts != 0) {
+            return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
+                              << memory.handle()->numInts << " numInts, but expected 0";
+        }
+        auto handle = nn::Memory::Ashmem{
+                .fd = NN_TRY(nn::dupFd(memory.handle()->data[0])),
+                .size = static_cast<size_t>(memory.size()),
+        };
+        return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
+    }
+
+    if (memory.name() == "mmap_fd") {
+        if (memory.handle()->numFds != 1) {
+            return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
+                              << memory.handle()->numFds << " numFds, but expected 1";
+        }
+        if (memory.handle()->numInts != 3) {
+            return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
+                              << memory.handle()->numInts << " numInts, but expected 3";
+        }
+
+        const int fd = memory.handle()->data[0];
+        const int prot = memory.handle()->data[1];
+        const int lower = memory.handle()->data[2];
+        const int higher = memory.handle()->data[3];
+        const size_t offset = nn::getOffsetFromInts(lower, higher);
+
+        return nn::createSharedMemoryFromFd(static_cast<size_t>(memory.size()), prot, fd, offset);
+    }
+
+    if (memory.name() != "hardware_buffer_blob") {
+        auto handle = nn::Memory::Unknown{
+                .handle = NN_TRY(unknownHandleFromNativeHandle(memory.handle())),
+                .size = static_cast<size_t>(memory.size()),
+                .name = memory.name(),
+        };
+        return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
+    }
+
+#ifdef __ANDROID__
+    constexpr auto roundUpToMultiple = [](uint32_t value, uint32_t multiple) -> uint32_t {
+        return (value + multiple - 1) / multiple * multiple;
+    };
+
+    const auto size = memory.size();
+    const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
+    const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+    const uint32_t width = size;
+    const uint32_t height = 1;  // height is always 1 for BLOB mode AHardwareBuffer.
+    const uint32_t layers = 1;  // layers is always 1 for BLOB mode AHardwareBuffer.
+
+    // AHardwareBuffer_createFromHandle() might fail because an allocator
+    // expects a specific stride value. In that case, we try to guess it by
+    // aligning the width to small powers of 2.
+    // TODO(b/174120849): Avoid stride assumptions.
+    AHardwareBuffer* hardwareBuffer = nullptr;
+    status_t status = UNKNOWN_ERROR;
+    for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
+        const uint32_t stride = roundUpToMultiple(width, alignment);
+        AHardwareBuffer_Desc desc{
+                .width = width,
+                .height = height,
+                .layers = layers,
+                .format = format,
+                .usage = usage,
+                .stride = stride,
+        };
+        status = AHardwareBuffer_createFromHandle(&desc, memory.handle(),
+                                                  AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
+                                                  &hardwareBuffer);
+        if (status == NO_ERROR) {
+            break;
+        }
+    }
+    if (status != NO_ERROR) {
+        return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
+               << "Can't create AHardwareBuffer from handle. Error: " << status;
+    }
+
+    return nn::createSharedMemoryFromAHWB(hardwareBuffer, /*takeOwnership=*/true);
+#else   // __ANDROID__
+    LOG(FATAL) << "nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const "
+                  "hidl_memory& memory): Not Available on Host Build";
+    return (NN_ERROR() << "createSharedMemoryFromHidlMemory failed")
+            .
+            operator nn::GeneralResult<nn::SharedMemory>();
+#endif  // __ANDROID__
+}
+
 }  // anonymous namespace
 
 GeneralResult<OperandType> unvalidatedConvert(const hal::V1_0::OperandType& operandType) {
@@ -146,8 +267,20 @@
     return Model::OperandValues(operandValues.data(), operandValues.size());
 }
 
+GeneralResult<SharedHandle> unvalidatedConvert(const hidl_handle& handle) {
+    if (handle.getNativeHandle() == nullptr) {
+        return nullptr;
+    }
+    if (handle->numFds != 1 || handle->numInts != 0) {
+        return NN_ERROR()
+               << "unvalidatedConvert failed because handle does not only hold a single fd";
+    }
+    auto duplicatedFd = NN_TRY(nn::dupFd(handle->data[0]));
+    return std::make_shared<const Handle>(std::move(duplicatedFd));
+}
+
 GeneralResult<SharedMemory> unvalidatedConvert(const hidl_memory& memory) {
-    return hal::utils::createSharedMemoryFromHidlMemory(memory);
+    return createSharedMemoryFromHidlMemory(memory);
 }
 
 GeneralResult<Model> unvalidatedConvert(const hal::V1_0::Model& model) {
@@ -155,7 +288,7 @@
 
     // Verify number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(model.operands.size(), operations));
+            NN_TRY(countNumberOfConsumers(model.operands.size(), operations));
     CHECK(model.operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < model.operands.size(); ++i) {
         if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) {
@@ -260,6 +393,82 @@
     return utils::unvalidatedConvert(canonical);
 }
 
+nn::GeneralResult<hidl_handle> createNativeHandleFrom(std::vector<base::unique_fd> fds,
+                                                      const std::vector<int32_t>& ints) {
+    constexpr size_t kIntMax = std::numeric_limits<int>::max();
+    CHECK_LE(fds.size(), kIntMax);
+    CHECK_LE(ints.size(), kIntMax);
+    native_handle_t* nativeHandle =
+            native_handle_create(static_cast<int>(fds.size()), static_cast<int>(ints.size()));
+    if (nativeHandle == nullptr) {
+        return NN_ERROR() << "Failed to create native_handle";
+    }
+
+    for (size_t i = 0; i < fds.size(); ++i) {
+        nativeHandle->data[i] = fds[i].release();
+    }
+    std::copy(ints.begin(), ints.end(), nativeHandle->data + nativeHandle->numFds);
+
+    hidl_handle handle;
+    handle.setTo(nativeHandle, /*shouldOwn=*/true);
+    return handle;
+}
+
+nn::GeneralResult<hidl_handle> createNativeHandleFrom(base::unique_fd fd,
+                                                      const std::vector<int32_t>& ints) {
+    std::vector<base::unique_fd> fds;
+    fds.push_back(std::move(fd));
+    return createNativeHandleFrom(std::move(fds), ints);
+}
+
+nn::GeneralResult<hidl_handle> createNativeHandleFrom(const nn::Memory::Unknown::Handle& handle) {
+    std::vector<base::unique_fd> fds = NN_TRY(nn::dupFds(handle.fds.begin(), handle.fds.end()));
+    return createNativeHandleFrom(std::move(fds), handle.ints);
+}
+
+nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Ashmem& memory) {
+    auto fd = NN_TRY(nn::dupFd(memory.fd));
+    auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), {}));
+    return hidl_memory("ashmem", std::move(handle), memory.size);
+}
+
+nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Fd& memory) {
+    auto fd = NN_TRY(nn::dupFd(memory.fd));
+
+    const auto [lowOffsetBits, highOffsetBits] = nn::getIntsFromOffset(memory.offset);
+    const std::vector<int> ints = {memory.prot, lowOffsetBits, highOffsetBits};
+
+    auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), ints));
+    return hidl_memory("mmap_fd", std::move(handle), memory.size);
+}
+
+nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::HardwareBuffer& memory) {
+#ifdef __ANDROID__
+    const auto* ahwb = memory.handle.get();
+    AHardwareBuffer_Desc bufferDesc;
+    AHardwareBuffer_describe(ahwb, &bufferDesc);
+
+    const bool isBlob = bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB;
+    const size_t size = isBlob ? bufferDesc.width : 0;
+    const char* const name = isBlob ? "hardware_buffer_blob" : "hardware_buffer";
+
+    const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
+    const hidl_handle hidlHandle(nativeHandle);
+    hidl_handle copiedHandle(hidlHandle);
+
+    return hidl_memory(name, std::move(copiedHandle), size);
+#else   // __ANDROID__
+    LOG(FATAL) << "nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const "
+                  "nn::Memory::HardwareBuffer& memory): Not Available on Host Build";
+    (void)memory;
+    return (NN_ERROR() << "createHidlMemoryFrom failed").operator nn::GeneralResult<hidl_memory>();
+#endif  // __ANDROID__
+}
+
+nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Unknown& memory) {
+    return hidl_memory(memory.name, NN_TRY(createNativeHandleFrom(memory.handle)), memory.size);
+}
+
 }  // anonymous namespace
 
 nn::GeneralResult<OperandType> unvalidatedConvert(const nn::OperandType& operandType) {
@@ -332,8 +541,19 @@
     return hidl_vec<uint8_t>(operandValues.data(), operandValues.data() + operandValues.size());
 }
 
+nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
+    if (handle == nullptr) {
+        return {};
+    }
+    base::unique_fd fd = NN_TRY(nn::dupFd(handle->get()));
+    return createNativeHandleFrom(std::move(fd), {});
+}
+
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
-    return hal::utils::createHidlMemoryFromSharedMemory(memory);
+    if (memory == nullptr) {
+        return NN_ERROR() << "Memory must be non-empty";
+    }
+    return std::visit([](const auto& x) { return createHidlMemoryFrom(x); }, memory->handle);
 }
 
 nn::GeneralResult<Model> unvalidatedConvert(const nn::Model& model) {
@@ -346,7 +566,7 @@
 
     // Update number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(operands.size(), model.main.operations));
+            NN_TRY(countNumberOfConsumers(operands.size(), model.main.operations));
     CHECK(operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < operands.size(); ++i) {
         operands[i].numberOfConsumers = numberOfConsumers[i];
diff --git a/neuralnetworks/1.0/utils/src/Device.cpp b/neuralnetworks/1.0/utils/src/Device.cpp
index 93bd81a..49913a2 100644
--- a/neuralnetworks/1.0/utils/src/Device.cpp
+++ b/neuralnetworks/1.0/utils/src/Device.cpp
@@ -18,6 +18,8 @@
 
 #include "Callbacks.h"
 #include "Conversions.h"
+#include "HandleError.h"
+#include "ProtectCallback.h"
 #include "Utils.h"
 
 #include <android/hardware/neuralnetworks/1.0/IDevice.h>
@@ -29,8 +31,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <functional>
@@ -47,7 +47,7 @@
 
 nn::GeneralResult<nn::Capabilities> capabilitiesCallback(ErrorStatus status,
                                                          const Capabilities& capabilities) {
-    HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getting capabilities failed with " << toString(status);
     return nn::convert(capabilities);
 }
 
@@ -156,7 +156,7 @@
 
     const auto ret = kDevice->prepareModel(hidlModel, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
 
     return cb->get();
 }
diff --git a/neuralnetworks/1.0/utils/src/Execution.cpp b/neuralnetworks/1.0/utils/src/Execution.cpp
index 7a3216b..6e105a6 100644
--- a/neuralnetworks/1.0/utils/src/Execution.cpp
+++ b/neuralnetworks/1.0/utils/src/Execution.cpp
@@ -18,6 +18,8 @@
 
 #include "Callbacks.h"
 #include "Conversions.h"
+#include "HandleError.h"
+#include "ProtectCallback.h"
 #include "Utils.h"
 
 #include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
@@ -27,8 +29,6 @@
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <memory>
 #include <utility>
diff --git a/neuralnetworks/1.0/utils/src/PreparedModel.cpp b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
index 3060c65..00e7d22 100644
--- a/neuralnetworks/1.0/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.0/utils/src/PreparedModel.cpp
@@ -20,6 +20,8 @@
 #include "Callbacks.h"
 #include "Conversions.h"
 #include "Execution.h"
+#include "HandleError.h"
+#include "ProtectCallback.h"
 #include "Utils.h"
 
 #include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
@@ -28,8 +30,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <memory>
 #include <tuple>
@@ -84,7 +84,7 @@
 
     const auto ret = kPreparedModel->execute(request, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
 
     auto result = NN_TRY(cb->get());
     if (relocation.output) {
diff --git a/neuralnetworks/utils/common/src/ProtectCallback.cpp b/neuralnetworks/1.0/utils/src/ProtectCallback.cpp
similarity index 98%
rename from neuralnetworks/utils/common/src/ProtectCallback.cpp
rename to neuralnetworks/1.0/utils/src/ProtectCallback.cpp
index 18e1f3b..89539b5 100644
--- a/neuralnetworks/utils/common/src/ProtectCallback.cpp
+++ b/neuralnetworks/1.0/utils/src/ProtectCallback.cpp
@@ -22,7 +22,8 @@
 #include <android/hidl/base/1.0/IBase.h>
 #include <hidl/HidlSupport.h>
 #include <nnapi/Result.h>
-#include <nnapi/hal/HandleError.h>
+
+#include "HandleError.h"
 
 #include <algorithm>
 #include <functional>
diff --git a/neuralnetworks/1.0/vts/functional/Android.bp b/neuralnetworks/1.0/vts/functional/Android.bp
index b33c581..29b31d2 100644
--- a/neuralnetworks/1.0/vts/functional/Android.bp
+++ b/neuralnetworks/1.0/vts/functional/Android.bp
@@ -50,7 +50,7 @@
         "libgmock",
         "libhidlmemory",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
+        "libneuralnetworks_common",
     ],
     header_libs: [
         "libneuralnetworks_headers",
@@ -81,7 +81,7 @@
         "libgmock",
         "libhidlmemory",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
+        "libneuralnetworks_common",
     ],
     whole_static_libs: [
         "neuralnetworks_generated_V1_0_example",
diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
index 5e224b5..d6bd36a 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Device.h
@@ -23,8 +23,8 @@
 #include <nnapi/OperandTypes.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <memory>
diff --git a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
index 09d9fe8..4660ff7 100644
--- a/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
+++ b/neuralnetworks/1.1/utils/include/nnapi/hal/1.1/Utils.h
@@ -26,7 +26,6 @@
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
 #include <nnapi/hal/1.0/Conversions.h>
-#include <nnapi/hal/HandleError.h>
 
 namespace android::hardware::neuralnetworks::V1_1::utils {
 
diff --git a/neuralnetworks/1.1/utils/src/Conversions.cpp b/neuralnetworks/1.1/utils/src/Conversions.cpp
index 467ceb3..5bdbe31 100644
--- a/neuralnetworks/1.1/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.1/utils/src/Conversions.cpp
@@ -100,7 +100,7 @@
 
     // Verify number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(model.operands.size(), operations));
+            NN_TRY(countNumberOfConsumers(model.operands.size(), operations));
     CHECK(model.operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < model.operands.size(); ++i) {
         if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) {
@@ -223,7 +223,7 @@
 
     // Update number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(operands.size(), model.main.operations));
+            NN_TRY(countNumberOfConsumers(operands.size(), model.main.operations));
     CHECK(operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < operands.size(); ++i) {
         operands[i].numberOfConsumers = numberOfConsumers[i];
diff --git a/neuralnetworks/1.1/utils/src/Device.cpp b/neuralnetworks/1.1/utils/src/Device.cpp
index 3197ef4..7d54cab 100644
--- a/neuralnetworks/1.1/utils/src/Device.cpp
+++ b/neuralnetworks/1.1/utils/src/Device.cpp
@@ -29,9 +29,9 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Callbacks.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <memory>
@@ -47,7 +47,7 @@
 
 nn::GeneralResult<nn::Capabilities> capabilitiesCallback(V1_0::ErrorStatus status,
                                                          const Capabilities& capabilities) {
-    HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getting capabilities failed with " << toString(status);
     return nn::convert(capabilities);
 }
 
@@ -157,7 +157,7 @@
 
     const auto ret = kDevice->prepareModel_1_1(hidlModel, hidlPreference, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
 
     return cb->get();
 }
diff --git a/neuralnetworks/1.1/vts/functional/Android.bp b/neuralnetworks/1.1/vts/functional/Android.bp
index c001112..e9d4b76 100644
--- a/neuralnetworks/1.1/vts/functional/Android.bp
+++ b/neuralnetworks/1.1/vts/functional/Android.bp
@@ -48,7 +48,7 @@
         "libgmock",
         "libhidlmemory",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
+        "libneuralnetworks_common",
     ],
     whole_static_libs: [
         "neuralnetworks_generated_V1_0_example",
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Burst.h
similarity index 82%
rename from neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
rename to neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Burst.h
index dae1ff3..ac9411c 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstController.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Burst.h
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_CONTROLLER_H
-#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_CONTROLLER_H
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_H
 
-#include "ExecutionBurstUtils.h"
+#include "nnapi/hal/1.2/BurstUtils.h"
 
 #include <android-base/thread_annotations.h>
 #include <android/hardware/neuralnetworks/1.0/types.h>
@@ -32,8 +32,8 @@
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <atomic>
 #include <chrono>
@@ -49,13 +49,11 @@
 namespace android::hardware::neuralnetworks::V1_2::utils {
 
 /**
- * The ExecutionBurstController class manages both the serialization and deserialization of data
- * across FMQ, making it appear to the runtime as a regular synchronous inference. Additionally,
- * this class manages the burst's memory cache.
+ * The Burst class manages both the serialization and deserialization of data across FMQ, making it
+ * appear to the runtime as a regular synchronous inference. Additionally, this class manages the
+ * burst's memory cache.
  */
-class ExecutionBurstController final
-    : public nn::IBurst,
-      public std::enable_shared_from_this<ExecutionBurstController> {
+class Burst final : public nn::IBurst, public std::enable_shared_from_this<Burst> {
     struct PrivateConstructorTag {};
 
   public:
@@ -150,21 +148,21 @@
      * Creates a burst controller on a prepared model.
      *
      * @param preparedModel Model prepared for execution to execute on.
-     * @param pollingTimeWindow How much time (in microseconds) the ExecutionBurstController is
-     *     allowed to poll the FMQ before waiting on the blocking futex. Polling may result in lower
-     *     latencies at the potential cost of more power usage.
-     * @return ExecutionBurstController Execution burst controller object.
+     * @param pollingTimeWindow How much time (in microseconds) the Burst is allowed to poll the FMQ
+     *     before waiting on the blocking futex. Polling may result in lower latencies at the
+     *     potential cost of more power usage.
+     * @return Burst Execution burst controller object.
      */
-    static nn::GeneralResult<std::shared_ptr<const ExecutionBurstController>> create(
+    static nn::GeneralResult<std::shared_ptr<const Burst>> create(
             nn::SharedPreparedModel preparedModel, const sp<IPreparedModel>& hidlPreparedModel,
             std::chrono::microseconds pollingTimeWindow);
 
-    ExecutionBurstController(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel,
-                             std::unique_ptr<RequestChannelSender> requestChannelSender,
-                             std::unique_ptr<ResultChannelReceiver> resultChannelReceiver,
-                             sp<ExecutionBurstCallback> callback, sp<IBurstContext> burstContext,
-                             std::shared_ptr<MemoryCache> memoryCache,
-                             neuralnetworks::utils::DeathHandler deathHandler);
+    Burst(PrivateConstructorTag tag, nn::SharedPreparedModel preparedModel,
+          std::unique_ptr<RequestChannelSender> requestChannelSender,
+          std::unique_ptr<ResultChannelReceiver> resultChannelReceiver,
+          sp<ExecutionBurstCallback> callback, sp<IBurstContext> burstContext,
+          std::shared_ptr<MemoryCache> memoryCache,
+          neuralnetworks::utils::DeathHandler deathHandler);
 
     // See IBurst::cacheMemory for information on this method.
     OptionalCacheHold cacheMemory(const nn::SharedMemory& memory) const override;
@@ -202,4 +200,4 @@
 
 }  // namespace android::hardware::neuralnetworks::V1_2::utils
 
-#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_CONTROLLER_H
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/BurstUtils.h
similarity index 97%
rename from neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h
rename to neuralnetworks/1.2/utils/include/nnapi/hal/1.2/BurstUtils.h
index c662bc3..7a6a241 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstUtils.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/BurstUtils.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
-#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_UTILS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_UTILS_H
 
 #include <android/hardware/neuralnetworks/1.0/types.h>
 #include <android/hardware/neuralnetworks/1.2/types.h>
@@ -23,7 +23,7 @@
 #include <hidl/MQDescriptor.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
-#include <nnapi/hal/ProtectCallback.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 
 #include <atomic>
 #include <chrono>
@@ -298,4 +298,4 @@
 
 }  // namespace android::hardware::neuralnetworks::V1_2::utils
 
-#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_UTILS_H
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_BURST_UTILS_H
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
index ba3c1ba..6dd8138 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Callbacks.h
@@ -27,8 +27,8 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Callbacks.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 // See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
index 272cee7..c3348aa 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Conversions.h
@@ -45,7 +45,6 @@
 GeneralResult<Extension> unvalidatedConvert(const hal::V1_2::Extension& extension);
 GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
         const hal::V1_2::Extension::OperandTypeInformation& operandTypeInformation);
-GeneralResult<SharedHandle> unvalidatedConvert(const hardware::hidl_handle& handle);
 
 GeneralResult<DeviceType> convert(const hal::V1_2::DeviceType& deviceType);
 GeneralResult<Capabilities> convert(const hal::V1_2::Capabilities& capabilities);
@@ -86,7 +85,6 @@
 nn::GeneralResult<Extension> unvalidatedConvert(const nn::Extension& extension);
 nn::GeneralResult<Extension::OperandTypeInformation> unvalidatedConvert(
         const nn::Extension::OperandTypeInformation& operandTypeInformation);
-nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle);
 
 nn::GeneralResult<DeviceType> convert(const nn::DeviceType& deviceType);
 nn::GeneralResult<Capabilities> convert(const nn::Capabilities& capabilities);
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
index b4bef5e..e7ac172 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Device.h
@@ -23,8 +23,8 @@
 #include <nnapi/OperandTypes.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <memory>
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Execution.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Execution.h
index 9c66446..867f181 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Execution.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Execution.h
@@ -21,8 +21,8 @@
 #include <nnapi/IExecution.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include "PreparedModel.h"
 
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
index 35abd79..1150e5e 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/PreparedModel.h
@@ -22,8 +22,8 @@
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <memory>
 #include <tuple>
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
index 5c3b8a7..23e336a 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
+++ b/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/Utils.h
@@ -28,7 +28,6 @@
 #include <nnapi/hal/1.0/Conversions.h>
 #include <nnapi/hal/1.1/Conversions.h>
 #include <nnapi/hal/1.1/Utils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <limits>
 
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp b/neuralnetworks/1.2/utils/src/Burst.cpp
similarity index 78%
rename from neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
rename to neuralnetworks/1.2/utils/src/Burst.cpp
index 2746965..e0a23f1 100644
--- a/neuralnetworks/1.2/utils/src/ExecutionBurstController.cpp
+++ b/neuralnetworks/1.2/utils/src/Burst.cpp
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "ExecutionBurstController"
-
-#include "ExecutionBurstController.h"
-#include "ExecutionBurstUtils.h"
+#include "Burst.h"
+#include "BurstUtils.h"
 
 #include <android-base/logging.h>
 #include <android-base/thread_annotations.h>
@@ -28,9 +26,9 @@
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
 #include <nnapi/hal/1.0/Conversions.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <algorithm>
@@ -57,14 +55,13 @@
 
   public:
     static nn::GeneralResult<std::shared_ptr<const BurstExecution>> create(
-            std::shared_ptr<const ExecutionBurstController> controller,
-            std::vector<FmqRequestDatum> request, hal::utils::RequestRelocation relocation,
-            std::vector<ExecutionBurstController::OptionalCacheHold> cacheHolds);
+            std::shared_ptr<const Burst> controller, std::vector<FmqRequestDatum> request,
+            hal::utils::RequestRelocation relocation,
+            std::vector<Burst::OptionalCacheHold> cacheHolds);
 
-    BurstExecution(PrivateConstructorTag tag,
-                   std::shared_ptr<const ExecutionBurstController> controller,
+    BurstExecution(PrivateConstructorTag tag, std::shared_ptr<const Burst> controller,
                    std::vector<FmqRequestDatum> request, hal::utils::RequestRelocation relocation,
-                   std::vector<ExecutionBurstController::OptionalCacheHold> cacheHolds);
+                   std::vector<Burst::OptionalCacheHold> cacheHolds);
 
     nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> compute(
             const nn::OptionalTimePoint& deadline) const override;
@@ -74,16 +71,16 @@
             const nn::OptionalDuration& timeoutDurationAfterFence) const override;
 
   private:
-    const std::shared_ptr<const ExecutionBurstController> kController;
+    const std::shared_ptr<const Burst> kController;
     const std::vector<FmqRequestDatum> kRequest;
     const hal::utils::RequestRelocation kRelocation;
-    const std::vector<ExecutionBurstController::OptionalCacheHold> kCacheHolds;
+    const std::vector<Burst::OptionalCacheHold> kCacheHolds;
 };
 
 nn::GeneralResult<sp<IBurstContext>> executionBurstResultCallback(
         V1_0::ErrorStatus status, const sp<IBurstContext>& burstContext) {
-    HANDLE_HAL_STATUS(status) << "IPreparedModel::configureExecutionBurst failed with status "
-                              << toString(status);
+    HANDLE_STATUS_HIDL(status) << "IPreparedModel::configureExecutionBurst failed with status "
+                               << toString(status);
     if (burstContext == nullptr) {
         return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
                << "IPreparedModel::configureExecutionBurst returned nullptr for burst";
@@ -92,8 +89,7 @@
 }
 
 nn::GeneralResult<hidl_vec<hidl_memory>> getMemoriesHelper(
-        const hidl_vec<int32_t>& slots,
-        const std::shared_ptr<ExecutionBurstController::MemoryCache>& memoryCache) {
+        const hidl_vec<int32_t>& slots, const std::shared_ptr<Burst::MemoryCache>& memoryCache) {
     hidl_vec<hidl_memory> memories(slots.size());
     for (size_t i = 0; i < slots.size(); ++i) {
         const int32_t slot = slots[i];
@@ -110,7 +106,7 @@
 
 // MemoryCache methods
 
-ExecutionBurstController::MemoryCache::MemoryCache() {
+Burst::MemoryCache::MemoryCache() {
     constexpr size_t kPreallocatedCount = 1024;
     std::vector<int32_t> freeSlotsSpace;
     freeSlotsSpace.reserve(kPreallocatedCount);
@@ -119,13 +115,13 @@
     mCacheCleaner.reserve(kPreallocatedCount);
 }
 
-void ExecutionBurstController::MemoryCache::setBurstContext(sp<IBurstContext> burstContext) {
+void Burst::MemoryCache::setBurstContext(sp<IBurstContext> burstContext) {
     std::lock_guard guard(mMutex);
     mBurstContext = std::move(burstContext);
 }
 
-std::pair<int32_t, ExecutionBurstController::MemoryCache::SharedCleanup>
-ExecutionBurstController::MemoryCache::cacheMemory(const nn::SharedMemory& memory) {
+std::pair<int32_t, Burst::MemoryCache::SharedCleanup> Burst::MemoryCache::cacheMemory(
+        const nn::SharedMemory& memory) {
     std::unique_lock lock(mMutex);
     base::ScopedLockAssertion lockAssert(mMutex);
 
@@ -163,7 +159,7 @@
     return std::make_pair(slot, std::move(cleaner));
 }
 
-nn::GeneralResult<nn::SharedMemory> ExecutionBurstController::MemoryCache::getMemory(int32_t slot) {
+nn::GeneralResult<nn::SharedMemory> Burst::MemoryCache::getMemory(int32_t slot) {
     std::lock_guard guard(mMutex);
     if (slot < 0 || static_cast<size_t>(slot) >= mMemoryCache.size()) {
         return NN_ERROR() << "Invalid slot: " << slot << " vs " << mMemoryCache.size();
@@ -171,7 +167,7 @@
     return mMemoryCache[slot];
 }
 
-void ExecutionBurstController::MemoryCache::freeMemory(const nn::SharedMemory& memory) {
+void Burst::MemoryCache::freeMemory(const nn::SharedMemory& memory) {
     {
         std::lock_guard guard(mMutex);
         const int32_t slot = mMemoryIdToSlot.at(memory);
@@ -189,7 +185,7 @@
     mCond.notify_all();
 }
 
-int32_t ExecutionBurstController::MemoryCache::allocateSlotLocked() {
+int32_t Burst::MemoryCache::allocateSlotLocked() {
     constexpr size_t kMaxNumberOfSlots = std::numeric_limits<int32_t>::max();
 
     // If there is a free slot, use it.
@@ -210,18 +206,18 @@
 
 // ExecutionBurstCallback methods
 
-ExecutionBurstController::ExecutionBurstCallback::ExecutionBurstCallback(
+Burst::ExecutionBurstCallback::ExecutionBurstCallback(
         const std::shared_ptr<MemoryCache>& memoryCache)
     : kMemoryCache(memoryCache) {
     CHECK(memoryCache != nullptr);
 }
 
-Return<void> ExecutionBurstController::ExecutionBurstCallback::getMemories(
-        const hidl_vec<int32_t>& slots, getMemories_cb cb) {
+Return<void> Burst::ExecutionBurstCallback::getMemories(const hidl_vec<int32_t>& slots,
+                                                        getMemories_cb cb) {
     const auto memoryCache = kMemoryCache.lock();
     if (memoryCache == nullptr) {
-        LOG(ERROR) << "ExecutionBurstController::ExecutionBurstCallback::getMemories called after "
-                      "the MemoryCache has been freed";
+        LOG(ERROR) << "Burst::ExecutionBurstCallback::getMemories called after the MemoryCache has "
+                      "been freed";
         cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
         return Void();
     }
@@ -229,8 +225,8 @@
     const auto maybeMemories = getMemoriesHelper(slots, memoryCache);
     if (!maybeMemories.has_value()) {
         const auto& [message, code] = maybeMemories.error();
-        LOG(ERROR) << "ExecutionBurstController::ExecutionBurstCallback::getMemories failed with "
-                   << code << ": " << message;
+        LOG(ERROR) << "Burst::ExecutionBurstCallback::getMemories failed with " << code << ": "
+                   << message;
         cb(V1_0::ErrorStatus::INVALID_ARGUMENT, {});
         return Void();
     }
@@ -239,14 +235,14 @@
     return Void();
 }
 
-// ExecutionBurstController methods
+// Burst methods
 
-nn::GeneralResult<std::shared_ptr<const ExecutionBurstController>> ExecutionBurstController::create(
+nn::GeneralResult<std::shared_ptr<const Burst>> Burst::create(
         nn::SharedPreparedModel preparedModel, const sp<V1_2::IPreparedModel>& hidlPreparedModel,
         std::chrono::microseconds pollingTimeWindow) {
     // check inputs
     if (preparedModel == nullptr || hidlPreparedModel == nullptr) {
-        return NN_ERROR() << "ExecutionBurstController::create passed a nullptr";
+        return NN_ERROR() << "Burst::create passed a nullptr";
     }
 
     // create FMQ objects
@@ -282,18 +278,18 @@
     deathHandler.protectCallbackForLifetimeOfDeathHandler(resultChannelReceiver.get());
 
     // make and return controller
-    return std::make_shared<const ExecutionBurstController>(
+    return std::make_shared<const Burst>(
             PrivateConstructorTag{}, std::move(preparedModel), std::move(requestChannelSender),
             std::move(resultChannelReceiver), std::move(burstCallback), std::move(burstContext),
             std::move(memoryCache), std::move(deathHandler));
 }
 
-ExecutionBurstController::ExecutionBurstController(
-        PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedModel,
-        std::unique_ptr<RequestChannelSender> requestChannelSender,
-        std::unique_ptr<ResultChannelReceiver> resultChannelReceiver,
-        sp<ExecutionBurstCallback> callback, sp<IBurstContext> burstContext,
-        std::shared_ptr<MemoryCache> memoryCache, neuralnetworks::utils::DeathHandler deathHandler)
+Burst::Burst(PrivateConstructorTag /*tag*/, nn::SharedPreparedModel preparedModel,
+             std::unique_ptr<RequestChannelSender> requestChannelSender,
+             std::unique_ptr<ResultChannelReceiver> resultChannelReceiver,
+             sp<ExecutionBurstCallback> callback, sp<IBurstContext> burstContext,
+             std::shared_ptr<MemoryCache> memoryCache,
+             neuralnetworks::utils::DeathHandler deathHandler)
     : kPreparedModel(std::move(preparedModel)),
       mRequestChannelSender(std::move(requestChannelSender)),
       mResultChannelReceiver(std::move(resultChannelReceiver)),
@@ -302,21 +298,20 @@
       mMemoryCache(std::move(memoryCache)),
       kDeathHandler(std::move(deathHandler)) {}
 
-ExecutionBurstController::OptionalCacheHold ExecutionBurstController::cacheMemory(
-        const nn::SharedMemory& memory) const {
+Burst::OptionalCacheHold Burst::cacheMemory(const nn::SharedMemory& memory) const {
     auto [slot, hold] = mMemoryCache->cacheMemory(memory);
     return hold;
 }
 
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-ExecutionBurstController::execute(const nn::Request& request, nn::MeasureTiming measure,
-                                  const nn::OptionalTimePoint& deadline,
-                                  const nn::OptionalDuration& loopTimeoutDuration) const {
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::execute(
+        const nn::Request& request, nn::MeasureTiming measure,
+        const nn::OptionalTimePoint& deadline,
+        const nn::OptionalDuration& loopTimeoutDuration) const {
     // This is the first point when we know an execution is occurring, so begin to collect
     // systraces. Note that the first point we can begin collecting systraces in
     // ExecutionBurstServer is when the RequestChannelReceiver realizes there is data in the FMQ, so
     // ExecutionBurstServer collects systraces at different points in the code.
-    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::execute");
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "Burst::execute");
 
     // if the request is valid but of a higher version than what's supported in burst execution,
     // fall back to another execution path
@@ -357,10 +352,10 @@
 }
 
 // See IBurst::createReusableExecution for information on this method.
-nn::GeneralResult<nn::SharedExecution> ExecutionBurstController::createReusableExecution(
+nn::GeneralResult<nn::SharedExecution> Burst::createReusableExecution(
         const nn::Request& request, nn::MeasureTiming measure,
         const nn::OptionalDuration& loopTimeoutDuration) const {
-    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ExecutionBurstController::createReusableExecution");
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "Burst::createReusableExecution");
 
     // if the request is valid but of a higher version than what's supported in burst execution,
     // fall back to another execution path
@@ -397,12 +392,10 @@
                                   std::move(relocation), std::move(holds));
 }
 
-nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>>
-ExecutionBurstController::executeInternal(const std::vector<FmqRequestDatum>& requestPacket,
-                                          const hal::utils::RequestRelocation& relocation,
-                                          FallbackFunction fallback) const {
-    NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
-                 "ExecutionBurstController::executeInternal");
+nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> Burst::executeInternal(
+        const std::vector<FmqRequestDatum>& requestPacket,
+        const hal::utils::RequestRelocation& relocation, FallbackFunction fallback) const {
+    NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION, "Burst::executeInternal");
 
     // Ensure that at most one execution is in flight at any given time.
     const bool alreadyInFlight = mExecutionInFlight.test_and_set();
@@ -435,9 +428,9 @@
 }
 
 nn::GeneralResult<std::shared_ptr<const BurstExecution>> BurstExecution::create(
-        std::shared_ptr<const ExecutionBurstController> controller,
-        std::vector<FmqRequestDatum> request, hal::utils::RequestRelocation relocation,
-        std::vector<ExecutionBurstController::OptionalCacheHold> cacheHolds) {
+        std::shared_ptr<const Burst> controller, std::vector<FmqRequestDatum> request,
+        hal::utils::RequestRelocation relocation,
+        std::vector<Burst::OptionalCacheHold> cacheHolds) {
     if (controller == nullptr) {
         return NN_ERROR() << "V1_2::utils::BurstExecution::create must have non-null controller";
     }
@@ -448,10 +441,10 @@
 }
 
 BurstExecution::BurstExecution(PrivateConstructorTag /*tag*/,
-                               std::shared_ptr<const ExecutionBurstController> controller,
+                               std::shared_ptr<const Burst> controller,
                                std::vector<FmqRequestDatum> request,
                                hal::utils::RequestRelocation relocation,
-                               std::vector<ExecutionBurstController::OptionalCacheHold> cacheHolds)
+                               std::vector<Burst::OptionalCacheHold> cacheHolds)
     : kController(std::move(controller)),
       kRequest(std::move(request)),
       kRelocation(std::move(relocation)),
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp b/neuralnetworks/1.2/utils/src/BurstUtils.cpp
similarity index 99%
rename from neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
rename to neuralnetworks/1.2/utils/src/BurstUtils.cpp
index 1bdde1e..b589c46 100644
--- a/neuralnetworks/1.2/utils/src/ExecutionBurstUtils.cpp
+++ b/neuralnetworks/1.2/utils/src/BurstUtils.cpp
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "ExecutionBurstUtils"
-
-#include "ExecutionBurstUtils.h"
+#include "BurstUtils.h"
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -27,7 +25,7 @@
 #include <hidl/MQDescriptor.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
-#include <nnapi/hal/ProtectCallback.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 
 #include <atomic>
 #include <chrono>
diff --git a/neuralnetworks/1.2/utils/src/Callbacks.cpp b/neuralnetworks/1.2/utils/src/Callbacks.cpp
index 01b5e12..cb61f21 100644
--- a/neuralnetworks/1.2/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.2/utils/src/Callbacks.cpp
@@ -29,10 +29,10 @@
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Callbacks.h>
 #include <nnapi/hal/1.0/Conversions.h>
+#include <nnapi/hal/1.0/HandleError.h>
 #include <nnapi/hal/1.0/PreparedModel.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <utility>
@@ -62,7 +62,7 @@
 
 nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
         V1_0::ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
     return NN_TRY(PreparedModel::create(preparedModel, /*executeSynchronously=*/true));
 }
 
@@ -74,7 +74,7 @@
         return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
                << "execution failed with " << toString(status);
     }
-    HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
     return convertExecutionGeneralResultsHelper(outputShapes, timing);
 }
 
diff --git a/neuralnetworks/1.2/utils/src/Conversions.cpp b/neuralnetworks/1.2/utils/src/Conversions.cpp
index 6a80b42..838d9c4 100644
--- a/neuralnetworks/1.2/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.2/utils/src/Conversions.cpp
@@ -28,7 +28,6 @@
 #include <nnapi/hal/1.0/Conversions.h>
 #include <nnapi/hal/1.1/Conversions.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <algorithm>
 #include <functional>
@@ -187,7 +186,7 @@
 
     // Verify number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(model.operands.size(), operations));
+            NN_TRY(countNumberOfConsumers(model.operands.size(), operations));
     CHECK(model.operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < model.operands.size(); ++i) {
         if (model.operands[i].numberOfConsumers != numberOfConsumers[i]) {
@@ -264,14 +263,6 @@
     };
 }
 
-GeneralResult<SharedHandle> unvalidatedConvert(const hidl_handle& hidlHandle) {
-    if (hidlHandle.getNativeHandle() == nullptr) {
-        return nullptr;
-    }
-    auto handle = NN_TRY(hal::utils::sharedHandleFromNativeHandle(hidlHandle.getNativeHandle()));
-    return std::make_shared<const Handle>(std::move(handle));
-}
-
 GeneralResult<DeviceType> convert(const hal::V1_2::DeviceType& deviceType) {
     return validatedConvert(deviceType);
 }
@@ -334,6 +325,10 @@
     return V1_0::utils::unvalidatedConvert(operandValues);
 }
 
+nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
+    return V1_0::utils::unvalidatedConvert(handle);
+}
+
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
     return V1_0::utils::unvalidatedConvert(memory);
 }
@@ -481,7 +476,7 @@
 
     // Update number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(operands.size(), model.main.operations));
+            NN_TRY(countNumberOfConsumers(operands.size(), model.main.operations));
     CHECK(operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < operands.size(); ++i) {
         operands[i].numberOfConsumers = numberOfConsumers[i];
@@ -544,13 +539,6 @@
     };
 }
 
-nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
-    if (handle == nullptr) {
-        return {};
-    }
-    return hal::utils::hidlHandleFromSharedHandle(*handle);
-}
-
 nn::GeneralResult<DeviceType> convert(const nn::DeviceType& deviceType) {
     return validatedConvert(deviceType);
 }
diff --git a/neuralnetworks/1.2/utils/src/Device.cpp b/neuralnetworks/1.2/utils/src/Device.cpp
index 9fe0de2..f12669a 100644
--- a/neuralnetworks/1.2/utils/src/Device.cpp
+++ b/neuralnetworks/1.2/utils/src/Device.cpp
@@ -30,10 +30,10 @@
 #include <nnapi/OperandTypes.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/1.1/Conversions.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <memory>
@@ -49,31 +49,31 @@
 
 nn::GeneralResult<nn::Capabilities> capabilitiesCallback(V1_0::ErrorStatus status,
                                                          const Capabilities& capabilities) {
-    HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getting capabilities failed with " << toString(status);
     return nn::convert(capabilities);
 }
 
 nn::GeneralResult<std::string> versionStringCallback(V1_0::ErrorStatus status,
                                                      const hidl_string& versionString) {
-    HANDLE_HAL_STATUS(status) << "getVersionString failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getVersionString failed with " << toString(status);
     return versionString;
 }
 
 nn::GeneralResult<nn::DeviceType> deviceTypeCallback(V1_0::ErrorStatus status,
                                                      DeviceType deviceType) {
-    HANDLE_HAL_STATUS(status) << "getDeviceType failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getDeviceType failed with " << toString(status);
     return nn::convert(deviceType);
 }
 
 nn::GeneralResult<std::vector<nn::Extension>> supportedExtensionsCallback(
         V1_0::ErrorStatus status, const hidl_vec<Extension>& extensions) {
-    HANDLE_HAL_STATUS(status) << "getExtensions failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getExtensions failed with " << toString(status);
     return nn::convert(extensions);
 }
 
 nn::GeneralResult<std::pair<uint32_t, uint32_t>> numberOfCacheFilesNeededCallback(
         V1_0::ErrorStatus status, uint32_t numModelCache, uint32_t numDataCache) {
-    HANDLE_HAL_STATUS(status) << "getNumberOfCacheFilesNeeded failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getNumberOfCacheFilesNeeded failed with " << toString(status);
     if (numModelCache > nn::kMaxNumberOfCacheFiles) {
         return NN_ERROR() << "getNumberOfCacheFilesNeeded returned numModelCache files greater "
                              "than allowed max ("
@@ -254,7 +254,7 @@
     const auto ret = kDevice->prepareModel_1_2(hidlModel, hidlPreference, hidlModelCache,
                                                hidlDataCache, hidlToken, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
 
     return cb->get();
 }
@@ -271,7 +271,7 @@
 
     const auto ret = kDevice->prepareModelFromCache(hidlModelCache, hidlDataCache, hidlToken, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation from cache failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation from cache failed with " << toString(status);
 
     return cb->get();
 }
diff --git a/neuralnetworks/1.2/utils/src/Execution.cpp b/neuralnetworks/1.2/utils/src/Execution.cpp
index 18d1c90..320b0e1 100644
--- a/neuralnetworks/1.2/utils/src/Execution.cpp
+++ b/neuralnetworks/1.2/utils/src/Execution.cpp
@@ -29,7 +29,6 @@
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <memory>
 #include <utility>
diff --git a/neuralnetworks/1.2/utils/src/PreparedModel.cpp b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
index c261184..6df3df3 100644
--- a/neuralnetworks/1.2/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.2/utils/src/PreparedModel.cpp
@@ -16,11 +16,11 @@
 
 #include "PreparedModel.h"
 
+#include "Burst.h"
+#include "BurstUtils.h"
 #include "Callbacks.h"
 #include "Conversions.h"
 #include "Execution.h"
-#include "ExecutionBurstController.h"
-#include "ExecutionBurstUtils.h"
 #include "Utils.h"
 
 #include <android/hardware/neuralnetworks/1.0/types.h>
@@ -31,9 +31,9 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Conversions.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <chrono>
 #include <memory>
@@ -82,7 +82,7 @@
     const auto ret = kPreparedModel->execute_1_2(request, measure, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
     if (status != V1_0::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
-        HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+        HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
     }
 
     return cb->get();
@@ -150,16 +150,8 @@
 }
 
 nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
-    auto self = shared_from_this();
-    auto fallback = [preparedModel = std::move(self)](
-                            const nn::Request& request, nn::MeasureTiming measure,
-                            const nn::OptionalTimePoint& deadline,
-                            const nn::OptionalDuration& loopTimeoutDuration)
-            -> nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> {
-        return preparedModel->execute(request, measure, deadline, loopTimeoutDuration);
-    };
     const auto pollingTimeWindow = getBurstControllerPollingTimeWindow();
-    return ExecutionBurstController::create(shared_from_this(), kPreparedModel, pollingTimeWindow);
+    return Burst::create(shared_from_this(), kPreparedModel, pollingTimeWindow);
 }
 
 std::any PreparedModel::getUnderlyingResource() const {
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index e313b47..52d51e2 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -71,7 +71,7 @@
         "libgmock",
         "libhidlmemory",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
+        "libneuralnetworks_common",
     ],
     whole_static_libs: [
         "neuralnetworks_generated_V1_0_example",
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
index 643172e..4b8ddc1 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Callbacks.h
@@ -30,8 +30,8 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Callbacks.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 // See hardware/interfaces/neuralnetworks/utils/README.md for more information on HIDL interface
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
index b677c62..ec1e530 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Conversions.h
@@ -113,6 +113,9 @@
 nn::GeneralResult<V1_2::MeasureTiming> convert(const nn::MeasureTiming& measureTiming);
 nn::GeneralResult<V1_2::Timing> convert(const nn::Timing& timing);
 
+nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
+        const std::vector<nn::SyncFence>& fences);
+
 }  // namespace android::hardware::neuralnetworks::V1_3::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_3_CONVERSIONS_H
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
index 84f606a..c3c6fc4 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Device.h
@@ -23,8 +23,8 @@
 #include <nnapi/OperandTypes.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <memory>
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
index 5acba71..480438d 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/PreparedModel.h
@@ -21,8 +21,8 @@
 #include <nnapi/IPreparedModel.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <memory>
 #include <tuple>
diff --git a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
index 28525bd..2812db2 100644
--- a/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
+++ b/neuralnetworks/1.3/utils/include/nnapi/hal/1.3/Utils.h
@@ -30,7 +30,6 @@
 #include <nnapi/hal/1.1/Utils.h>
 #include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/1.2/Utils.h>
-#include <nnapi/hal/HandleError.h>
 
 namespace android::hardware::neuralnetworks::V1_3::utils {
 
diff --git a/neuralnetworks/1.3/utils/src/Buffer.cpp b/neuralnetworks/1.3/utils/src/Buffer.cpp
index ada5265..34925ea 100644
--- a/neuralnetworks/1.3/utils/src/Buffer.cpp
+++ b/neuralnetworks/1.3/utils/src/Buffer.cpp
@@ -25,7 +25,7 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Conversions.h>
-#include <nnapi/hal/HandleError.h>
+#include <nnapi/hal/1.0/HandleError.h>
 
 #include "Conversions.h"
 #include "Utils.h"
@@ -66,7 +66,7 @@
 
     const auto ret = kBuffer->copyTo(hidlDst);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "IBuffer::copyTo failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "IBuffer::copyTo failed with " << toString(status);
 
     return {};
 }
@@ -78,7 +78,7 @@
 
     const auto ret = kBuffer->copyFrom(hidlSrc, hidlDimensions);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "IBuffer::copyFrom failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "IBuffer::copyFrom failed with " << toString(status);
 
     return {};
 }
diff --git a/neuralnetworks/1.3/utils/src/Callbacks.cpp b/neuralnetworks/1.3/utils/src/Callbacks.cpp
index 156216f..f063862 100644
--- a/neuralnetworks/1.3/utils/src/Callbacks.cpp
+++ b/neuralnetworks/1.3/utils/src/Callbacks.cpp
@@ -30,13 +30,13 @@
 #include <nnapi/Types.h>
 #include <nnapi/hal/1.0/Callbacks.h>
 #include <nnapi/hal/1.0/Conversions.h>
+#include <nnapi/hal/1.0/HandleError.h>
 #include <nnapi/hal/1.0/PreparedModel.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/1.2/Callbacks.h>
 #include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/1.2/PreparedModel.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <utility>
@@ -71,13 +71,13 @@
 
 nn::GeneralResult<std::vector<bool>> supportedOperationsCallback(
         ErrorStatus status, const hidl_vec<bool>& supportedOperations) {
-    HANDLE_HAL_STATUS(status) << "get supported operations failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "get supported operations failed with " << toString(status);
     return supportedOperations;
 }
 
 nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
         ErrorStatus status, const sp<IPreparedModel>& preparedModel) {
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
     return NN_TRY(PreparedModel::create(preparedModel, /*executeSynchronously=*/true));
 }
 
@@ -90,7 +90,7 @@
         return NN_ERROR(nn::ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, std::move(canonicalOutputShapes))
                << "execution failed with " << toString(status);
     }
-    HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
     return convertExecutionGeneralResultsHelper(outputShapes, timing);
 }
 
diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp
index b35b2cd..a1d414c 100644
--- a/neuralnetworks/1.3/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.3/utils/src/Conversions.cpp
@@ -28,7 +28,6 @@
 #include <nnapi/hal/1.0/Conversions.h>
 #include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <algorithm>
 #include <chrono>
@@ -194,7 +193,7 @@
 
     // Verify number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(subgraph.operands.size(), operations));
+            NN_TRY(countNumberOfConsumers(subgraph.operands.size(), operations));
     CHECK(subgraph.operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < subgraph.operands.size(); ++i) {
         if (subgraph.operands[i].numberOfConsumers != numberOfConsumers[i]) {
@@ -380,7 +379,7 @@
 }
 
 nn::GeneralResult<hidl_handle> unvalidatedConvert(const nn::SharedHandle& handle) {
-    return V1_2::utils::unvalidatedConvert(handle);
+    return V1_0::utils::unvalidatedConvert(handle);
 }
 
 nn::GeneralResult<hidl_memory> unvalidatedConvert(const nn::SharedMemory& memory) {
@@ -543,7 +542,7 @@
 
     // Update number of consumers.
     const auto numberOfConsumers =
-            NN_TRY(hal::utils::countNumberOfConsumers(operands.size(), subgraph.operations));
+            NN_TRY(countNumberOfConsumers(operands.size(), subgraph.operations));
     CHECK(operands.size() == numberOfConsumers.size());
     for (size_t i = 0; i < operands.size(); ++i) {
         operands[i].numberOfConsumers = numberOfConsumers[i];
@@ -727,4 +726,13 @@
     return V1_2::utils::convert(timing);
 }
 
+nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
+        const std::vector<nn::SyncFence>& syncFences) {
+    std::vector<nn::SharedHandle> handles;
+    handles.reserve(syncFences.size());
+    std::transform(syncFences.begin(), syncFences.end(), std::back_inserter(handles),
+                   [](const nn::SyncFence& syncFence) { return syncFence.getSharedHandle(); });
+    return convert(handles);
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_3::utils
diff --git a/neuralnetworks/1.3/utils/src/Device.cpp b/neuralnetworks/1.3/utils/src/Device.cpp
index d710b85..a73ce82 100644
--- a/neuralnetworks/1.3/utils/src/Device.cpp
+++ b/neuralnetworks/1.3/utils/src/Device.cpp
@@ -33,13 +33,13 @@
 #include <nnapi/OperandTypes.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
 #include <nnapi/hal/1.1/Conversions.h>
 #include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/1.2/Device.h>
 #include <nnapi/hal/1.2/Utils.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <any>
 #include <functional>
@@ -72,7 +72,7 @@
 
 nn::GeneralResult<nn::Capabilities> capabilitiesCallback(ErrorStatus status,
                                                          const Capabilities& capabilities) {
-    HANDLE_HAL_STATUS(status) << "getting capabilities failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getting capabilities failed with " << toString(status);
     return nn::convert(capabilities);
 }
 
@@ -89,7 +89,7 @@
 
 nn::GeneralResult<nn::SharedBuffer> allocationCallback(ErrorStatus status,
                                                        const sp<IBuffer>& buffer, uint32_t token) {
-    HANDLE_HAL_STATUS(status) << "IDevice::allocate failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "IDevice::allocate failed with " << toString(status);
     return Buffer::create(buffer, static_cast<nn::Request::MemoryDomainToken>(token));
 }
 
@@ -208,7 +208,7 @@
             kDevice->prepareModel_1_3(hidlModel, hidlPreference, hidlPriority, hidlDeadline,
                                       hidlModelCache, hidlDataCache, hidlToken, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation failed with " << toString(status);
 
     return cb->get();
 }
@@ -227,7 +227,7 @@
     const auto ret = kDevice->prepareModelFromCache_1_3(hidlDeadline, hidlModelCache, hidlDataCache,
                                                         hidlToken, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
-    HANDLE_HAL_STATUS(status) << "model preparation from cache failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "model preparation from cache failed with " << toString(status);
 
     return cb->get();
 }
diff --git a/neuralnetworks/1.3/utils/src/Execution.cpp b/neuralnetworks/1.3/utils/src/Execution.cpp
index 4dc0ddf..0ec7f56 100644
--- a/neuralnetworks/1.3/utils/src/Execution.cpp
+++ b/neuralnetworks/1.3/utils/src/Execution.cpp
@@ -29,7 +29,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <memory>
 #include <utility>
@@ -73,7 +72,7 @@
 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> Execution::computeFenced(
         const std::vector<nn::SyncFence>& waitFor, const nn::OptionalTimePoint& deadline,
         const nn::OptionalDuration& timeoutDurationAfterFence) const {
-    const auto hidlWaitFor = NN_TRY(hal::utils::convertSyncFences(waitFor));
+    const auto hidlWaitFor = NN_TRY(convertSyncFences(waitFor));
     const auto hidlDeadline = NN_TRY(convert(deadline));
     const auto hidlTimeoutDurationAfterFence = NN_TRY(convert(timeoutDurationAfterFence));
     return kPreparedModel->executeFencedInternal(kRequest, hidlWaitFor, kMeasure, hidlDeadline,
diff --git a/neuralnetworks/1.3/utils/src/PreparedModel.cpp b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
index d5dee9d..ce977e5 100644
--- a/neuralnetworks/1.3/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/1.3/utils/src/PreparedModel.cpp
@@ -30,12 +30,12 @@
 #include <nnapi/Result.h>
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
+#include <nnapi/hal/1.2/Burst.h>
+#include <nnapi/hal/1.2/BurstUtils.h>
 #include <nnapi/hal/1.2/Conversions.h>
-#include <nnapi/hal/1.2/ExecutionBurstController.h>
-#include <nnapi/hal/1.2/ExecutionBurstUtils.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <memory>
 #include <tuple>
@@ -50,14 +50,14 @@
 
 nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionCallbackResults(
         ErrorStatus status, const V1_2::Timing& timingLaunched, const V1_2::Timing& timingFenced) {
-    HANDLE_HAL_STATUS(status) << "fenced execution callback info failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "fenced execution callback info failed with " << toString(status);
     return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
 }
 
 nn::GeneralResult<std::pair<nn::SyncFence, nn::ExecuteFencedInfoCallback>> fencedExecutionCallback(
         ErrorStatus status, const hidl_handle& syncFence,
         const sp<IFencedExecutionCallback>& callback) {
-    HANDLE_HAL_STATUS(status) << "fenced execution failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "fenced execution failed with " << toString(status);
 
     auto resultSyncFence = nn::SyncFence::createAsSignaled();
     if (syncFence.getNativeHandle() != nullptr) {
@@ -127,7 +127,7 @@
             kPreparedModel->execute_1_3(request, measure, deadline, loopTimeoutDuration, cb);
     const auto status = HANDLE_TRANSPORT_FAILURE(ret);
     if (status != ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
-        HANDLE_HAL_STATUS(status) << "execution failed with " << toString(status);
+        HANDLE_STATUS_HIDL(status) << "execution failed with " << toString(status);
     }
 
     return cb->get();
@@ -186,7 +186,7 @@
             &maybeRequestInShared, &relocation));
 
     const auto hidlRequest = NN_TRY(convert(requestInShared));
-    const auto hidlWaitFor = NN_TRY(hal::utils::convertSyncFences(waitFor));
+    const auto hidlWaitFor = NN_TRY(convertSyncFences(waitFor));
     const auto hidlMeasure = NN_TRY(convert(measure));
     const auto hidlDeadline = NN_TRY(convert(deadline));
     const auto hidlLoopTimeoutDuration = NN_TRY(convert(loopTimeoutDuration));
@@ -246,17 +246,8 @@
 }
 
 nn::GeneralResult<nn::SharedBurst> PreparedModel::configureExecutionBurst() const {
-    auto self = shared_from_this();
-    auto fallback = [preparedModel = std::move(self)](
-                            const nn::Request& request, nn::MeasureTiming measure,
-                            const nn::OptionalTimePoint& deadline,
-                            const nn::OptionalDuration& loopTimeoutDuration)
-            -> nn::ExecutionResult<std::pair<std::vector<nn::OutputShape>, nn::Timing>> {
-        return preparedModel->execute(request, measure, deadline, loopTimeoutDuration);
-    };
     const auto pollingTimeWindow = V1_2::utils::getBurstControllerPollingTimeWindow();
-    return V1_2::utils::ExecutionBurstController::create(shared_from_this(), kPreparedModel,
-                                                         pollingTimeWindow);
+    return V1_2::utils::Burst::create(shared_from_this(), kPreparedModel, pollingTimeWindow);
 }
 
 std::any PreparedModel::getUnderlyingResource() const {
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index ab0a018..8951760 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -66,7 +66,6 @@
         "VtsHalNeuralNetworksV1_0_utils",
         "VtsHalNeuralNetworksV1_2_utils",
         "VtsHalNeuralNetworksV1_3_utils",
-        "android.hardware.neuralnetworks-V2-ndk",
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
         "android.hardware.neuralnetworks@1.2",
@@ -76,7 +75,7 @@
         "libgmock",
         "libhidlmemory",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
+        "libneuralnetworks_common",
         "libsync",
     ],
     whole_static_libs: [
diff --git a/neuralnetworks/aidl/utils/Android.bp b/neuralnetworks/aidl/utils/Android.bp
index 63cf45d..1a4cd9a 100644
--- a/neuralnetworks/aidl/utils/Android.bp
+++ b/neuralnetworks/aidl/utils/Android.bp
@@ -23,8 +23,8 @@
     default_applicable_licenses: ["hardware_interfaces_license"],
 }
 
-cc_library_static {
-    name: "neuralnetworks_utils_hal_aidl",
+cc_defaults {
+    name: "neuralnetworks_utils_hal_aidl_defaults",
     defaults: ["neuralnetworks_utils_defaults"],
     srcs: ["src/*"],
     local_include_dirs: ["include/nnapi/hal/aidl/"],
@@ -38,9 +38,7 @@
         "neuralnetworks_utils_hal_common",
     ],
     shared_libs: [
-        "android.hardware.neuralnetworks-V2-ndk",
         "libbinder_ndk",
-        "libhidlbase",
     ],
     target: {
         android: {
@@ -49,21 +47,49 @@
     },
 }
 
-cc_test {
-    name: "neuralnetworks_utils_hal_aidl_test",
-    defaults: ["neuralnetworks_utils_defaults"],
-    srcs: [
-        "test/*.cpp",
+cc_library_static {
+    name: "neuralnetworks_utils_hal_aidl_v1",
+    defaults: ["neuralnetworks_utils_hal_aidl_defaults"],
+    shared_libs: [
+        "android.hardware.neuralnetworks-V1-ndk",
     ],
+}
+
+cc_library_static {
+    name: "neuralnetworks_utils_hal_aidl",
+    defaults: ["neuralnetworks_utils_hal_aidl_defaults"],
+    shared_libs: [
+        "android.hardware.neuralnetworks-V2-ndk",
+    ],
+}
+
+// A cc_defaults that includes the latest non-experimental AIDL utilities and other AIDL libraries
+// that are commonly used together. Modules that always depend on the latest non-experimental
+// AIDL features can include this cc_defaults to avoid managing dependency versions explicitly.
+cc_defaults {
+    name: "neuralnetworks_use_latest_utils_hal_aidl",
     static_libs: [
         "android.hardware.common-V2-ndk",
         "android.hardware.graphics.common-V3-ndk",
         "android.hardware.neuralnetworks-V2-ndk",
+        "neuralnetworks_utils_hal_aidl",
+    ],
+}
+
+cc_test {
+    name: "neuralnetworks_utils_hal_aidl_test",
+    defaults: [
+        "neuralnetworks_use_latest_utils_hal_aidl",
+        "neuralnetworks_utils_defaults",
+    ],
+    srcs: [
+        "test/*.cpp",
+    ],
+    static_libs: [
         "libaidlcommonsupport",
         "libgmock",
         "libneuralnetworks_common",
         "neuralnetworks_types",
-        "neuralnetworks_utils_hal_aidl",
         "neuralnetworks_utils_hal_common",
     ],
     shared_libs: [
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/BufferTracker.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/BufferTracker.h
new file mode 100644
index 0000000..18d01e2
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/BufferTracker.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_BUFFER_TRACKER_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_BUFFER_TRACKER_H
+
+#include <android-base/macros.h>
+#include <android-base/thread_annotations.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <stack>
+#include <utility>
+#include <vector>
+
+#include "nnapi/hal/aidl/HalInterfaces.h"
+#include "nnapi/hal/aidl/ValidateHal.h"
+
+namespace android::nn {
+
+// This class manages a CPU buffer allocated on heap and provides validation methods.
+class AidlManagedBuffer {
+  public:
+    static std::shared_ptr<AidlManagedBuffer> create(uint32_t size,
+                                                     std::set<AidlHalPreparedModelRole> roles,
+                                                     const Operand& operand);
+
+    // Prefer AidlManagedBuffer::create.
+    AidlManagedBuffer(std::unique_ptr<uint8_t[]> buffer, uint32_t size,
+                      std::set<AidlHalPreparedModelRole> roles, const Operand& operand);
+
+    uint8_t* getPointer() const { return kBuffer.get(); }
+    uint32_t getSize() const { return kSize; }
+
+    // "poolIndex" is the index of this buffer in the request.pools.
+    ErrorStatus validateRequest(uint32_t poolIndex, const Request& request,
+                                const aidl_hal::IPreparedModel* preparedModel) const;
+
+    // "size" is the byte size of the Memory provided to the copyFrom or copyTo method.
+    ErrorStatus validateCopyFrom(const std::vector<uint32_t>& dimensions, uint32_t size) const;
+    ErrorStatus validateCopyTo(uint32_t size) const;
+
+    bool updateDimensions(const std::vector<uint32_t>& dimensions);
+    void setInitialized(bool initialized);
+
+  private:
+    mutable std::mutex mMutex;
+    const std::unique_ptr<uint8_t[]> kBuffer;
+    const uint32_t kSize;
+    const std::set<AidlHalPreparedModelRole> kRoles;
+    const OperandType kOperandType;
+    const std::vector<uint32_t> kInitialDimensions;
+    std::vector<uint32_t> mUpdatedDimensions GUARDED_BY(mMutex);
+    bool mInitialized GUARDED_BY(mMutex) = false;
+};
+
+// Keep track of all AidlManagedBuffers and assign each with a unique token.
+class AidlBufferTracker : public std::enable_shared_from_this<AidlBufferTracker> {
+    DISALLOW_COPY_AND_ASSIGN(AidlBufferTracker);
+
+  public:
+    // A RAII class to help manage the lifetime of the token.
+    // It is only supposed to be constructed in AidlBufferTracker::add.
+    class Token {
+        DISALLOW_COPY_AND_ASSIGN(Token);
+
+      public:
+        Token(uint32_t token, std::shared_ptr<AidlBufferTracker> tracker)
+            : kToken(token), kBufferTracker(std::move(tracker)) {}
+        ~Token() { kBufferTracker->free(kToken); }
+        uint32_t get() const { return kToken; }
+
+      private:
+        const uint32_t kToken;
+        const std::shared_ptr<AidlBufferTracker> kBufferTracker;
+    };
+
+    // The factory of AidlBufferTracker. This ensures that the AidlBufferTracker is always managed
+    // by a shared_ptr.
+    static std::shared_ptr<AidlBufferTracker> create() {
+        return std::make_shared<AidlBufferTracker>();
+    }
+
+    // Prefer AidlBufferTracker::create.
+    AidlBufferTracker() : mTokenToBuffers(1) {}
+
+    std::unique_ptr<Token> add(std::shared_ptr<AidlManagedBuffer> buffer);
+    std::shared_ptr<AidlManagedBuffer> get(uint32_t token) const;
+
+  private:
+    void free(uint32_t token);
+
+    mutable std::mutex mMutex;
+    std::stack<uint32_t, std::vector<uint32_t>> mFreeTokens GUARDED_BY(mMutex);
+
+    // Since the tokens are allocated in a non-sparse way, we use a vector to represent the mapping.
+    // The index of the vector is the token. When the token gets freed, the corresponding entry is
+    // set to nullptr. mTokenToBuffers[0] is always set to nullptr because 0 is an invalid token.
+    std::vector<std::shared_ptr<AidlManagedBuffer>> mTokenToBuffers GUARDED_BY(mMutex);
+};
+
+}  // namespace android::nn
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_BUFFER_TRACKER_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Callbacks.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Callbacks.h
index 8651912..168264b 100644
--- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Callbacks.h
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Callbacks.h
@@ -32,8 +32,7 @@
 namespace aidl::android::hardware::neuralnetworks::utils {
 
 // An AIDL callback class to receive the results of IDevice::prepareModel* asynchronously.
-class PreparedModelCallback final : public BnPreparedModelCallback,
-                                    public hal::utils::IProtectedCallback {
+class PreparedModelCallback final : public BnPreparedModelCallback, public IProtectedCallback {
   public:
     using Data = nn::GeneralResult<nn::SharedPreparedModel>;
 
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Device.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Device.h
index 1457646..d558f66 100644
--- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Device.h
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Device.h
@@ -43,11 +43,12 @@
 
   public:
     static nn::GeneralResult<std::shared_ptr<const Device>> create(
-            std::string name, std::shared_ptr<aidl_hal::IDevice> device);
+            std::string name, std::shared_ptr<aidl_hal::IDevice> device, nn::Version featureLevel);
 
     Device(PrivateConstructorTag tag, std::string name, std::string versionString,
-           nn::DeviceType deviceType, std::vector<nn::Extension> extensions,
-           nn::Capabilities capabilities, std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded,
+           nn::Version featureLevel, nn::DeviceType deviceType,
+           std::vector<nn::Extension> extensions, nn::Capabilities capabilities,
+           std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded,
            std::shared_ptr<aidl_hal::IDevice> device, DeathHandler deathHandler);
 
     const std::string& getName() const override;
@@ -84,6 +85,7 @@
   private:
     const std::string kName;
     const std::string kVersionString;
+    const nn::Version kFeatureLevel;
     const nn::DeviceType kDeviceType;
     const std::vector<nn::Extension> kExtensions;
     const nn::Capabilities kCapabilities;
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalInterfaces.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalInterfaces.h
new file mode 100644
index 0000000..3fb443c
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalInterfaces.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_INTERFACES_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_INTERFACES_H
+
+#include <aidl/android/hardware/neuralnetworks/BnBuffer.h>
+#include <aidl/android/hardware/neuralnetworks/BnBurst.h>
+#include <aidl/android/hardware/neuralnetworks/BnDevice.h>
+#include <aidl/android/hardware/neuralnetworks/BnFencedExecutionCallback.h>
+#include <aidl/android/hardware/neuralnetworks/BnPreparedModel.h>
+#include <aidl/android/hardware/neuralnetworks/BnPreparedModelCallback.h>
+#include <aidl/android/hardware/neuralnetworks/BufferDesc.h>
+#include <aidl/android/hardware/neuralnetworks/BufferRole.h>
+#include <aidl/android/hardware/neuralnetworks/Capabilities.h>
+#include <aidl/android/hardware/neuralnetworks/DataLocation.h>
+#include <aidl/android/hardware/neuralnetworks/DeviceBuffer.h>
+#include <aidl/android/hardware/neuralnetworks/DeviceType.h>
+#include <aidl/android/hardware/neuralnetworks/ErrorStatus.h>
+#include <aidl/android/hardware/neuralnetworks/ExecutionPreference.h>
+#include <aidl/android/hardware/neuralnetworks/Extension.h>
+#include <aidl/android/hardware/neuralnetworks/ExtensionNameAndPrefix.h>
+#include <aidl/android/hardware/neuralnetworks/ExtensionOperandTypeInformation.h>
+#include <aidl/android/hardware/neuralnetworks/FusedActivationFunc.h>
+#include <aidl/android/hardware/neuralnetworks/IBuffer.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
+#include <aidl/android/hardware/neuralnetworks/IFencedExecutionCallback.h>
+#include <aidl/android/hardware/neuralnetworks/IPreparedModel.h>
+#include <aidl/android/hardware/neuralnetworks/IPreparedModelCallback.h>
+#include <aidl/android/hardware/neuralnetworks/IPreparedModelParcel.h>
+#include <aidl/android/hardware/neuralnetworks/Memory.h>
+#include <aidl/android/hardware/neuralnetworks/Model.h>
+#include <aidl/android/hardware/neuralnetworks/NumberOfCacheFiles.h>
+#include <aidl/android/hardware/neuralnetworks/Operand.h>
+#include <aidl/android/hardware/neuralnetworks/OperandExtraParams.h>
+#include <aidl/android/hardware/neuralnetworks/OperandLifeTime.h>
+#include <aidl/android/hardware/neuralnetworks/OperandPerformance.h>
+#include <aidl/android/hardware/neuralnetworks/OperandType.h>
+#include <aidl/android/hardware/neuralnetworks/Operation.h>
+#include <aidl/android/hardware/neuralnetworks/OperationType.h>
+#include <aidl/android/hardware/neuralnetworks/OutputShape.h>
+#include <aidl/android/hardware/neuralnetworks/PerformanceInfo.h>
+#include <aidl/android/hardware/neuralnetworks/Priority.h>
+#include <aidl/android/hardware/neuralnetworks/Request.h>
+#include <aidl/android/hardware/neuralnetworks/RequestArgument.h>
+#include <aidl/android/hardware/neuralnetworks/RequestMemoryPool.h>
+#include <aidl/android/hardware/neuralnetworks/Subgraph.h>
+#include <aidl/android/hardware/neuralnetworks/SymmPerChannelQuantParams.h>
+#include <aidl/android/hardware/neuralnetworks/Timing.h>
+
+namespace android::nn {
+
+namespace aidl_hal = ::aidl::android::hardware::neuralnetworks;
+
+inline constexpr aidl_hal::Priority kDefaultPriorityAidl = aidl_hal::Priority::MEDIUM;
+
+}  // namespace android::nn
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_INTERFACES_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalUtils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalUtils.h
new file mode 100644
index 0000000..89552bc
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/HalUtils.h
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_UTILS_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_UTILS_H
+
+#include <vector>
+
+#include "nnapi/hal/aidl/HalInterfaces.h"
+
+namespace android {
+namespace nn {
+
+// Return a vector with one entry for each non-extension OperandType except
+// SUBGRAPH, set to the specified PerformanceInfo value.  The vector will be
+// sorted by OperandType.
+//
+// Control flow (OperandType::SUBGRAPH) operation performance is specified
+// separately using Capabilities::ifPerformance and
+// Capabilities::whilePerformance.
+std::vector<aidl_hal::OperandPerformance> nonExtensionOperandPerformance(
+        aidl_hal::PerformanceInfo perf);
+
+// Update the vector entry corresponding to the specified OperandType with the
+// specified PerformanceInfo value.  The vector must already have an entry for
+// that OperandType, and must be sorted by OperandType.
+void update(std::vector<aidl_hal::OperandPerformance>* operandPerformance,
+            aidl_hal::OperandType type, aidl_hal::PerformanceInfo perf);
+
+// Returns true if an operand type is an extension type.
+bool isExtensionOperandType(aidl_hal::OperandType type);
+
+// Returns true if an operand type is a scalar type.
+bool isNonExtensionScalar(aidl_hal::OperandType type);
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_HAL_UTILS_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ProtectCallback.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ProtectCallback.h
index ab1108c..92ed1cd 100644
--- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ProtectCallback.h
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ProtectCallback.h
@@ -23,7 +23,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <functional>
 #include <mutex>
@@ -34,19 +33,39 @@
 
 namespace aidl::android::hardware::neuralnetworks::utils {
 
+class IProtectedCallback {
+  public:
+    /**
+     * Marks this object as a dead object.
+     */
+    virtual void notifyAsDeadObject() = 0;
+
+    // Public virtual destructor to allow objects to be stored (and destroyed) as smart pointers.
+    // E.g., std::unique_ptr<IProtectedCallback>.
+    virtual ~IProtectedCallback() = default;
+
+  protected:
+    // Protect the non-destructor special member functions to prevent object slicing.
+    IProtectedCallback() = default;
+    IProtectedCallback(const IProtectedCallback&) = default;
+    IProtectedCallback(IProtectedCallback&&) noexcept = default;
+    IProtectedCallback& operator=(const IProtectedCallback&) = default;
+    IProtectedCallback& operator=(IProtectedCallback&&) noexcept = default;
+};
+
 // Thread safe class
 class DeathMonitor final {
   public:
     static void serviceDied(void* cookie);
     void serviceDied();
     // Precondition: `killable` must be non-null.
-    void add(hal::utils::IProtectedCallback* killable) const;
+    void add(IProtectedCallback* killable) const;
     // Precondition: `killable` must be non-null.
-    void remove(hal::utils::IProtectedCallback* killable) const;
+    void remove(IProtectedCallback* killable) const;
 
   private:
     mutable std::mutex mMutex;
-    mutable std::vector<hal::utils::IProtectedCallback*> mObjects GUARDED_BY(mMutex);
+    mutable std::vector<IProtectedCallback*> mObjects GUARDED_BY(mMutex);
 };
 
 class DeathHandler final {
@@ -62,7 +81,7 @@
     using Cleanup = std::function<void()>;
     // Precondition: `killable` must be non-null.
     [[nodiscard]] ::android::base::ScopeGuard<Cleanup> protectCallback(
-            hal::utils::IProtectedCallback* killable) const;
+            IProtectedCallback* killable) const;
 
     std::shared_ptr<DeathMonitor> getDeathMonitor() const { return kDeathMonitor; }
 
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
index 1b149e4..f2ab479 100644
--- a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/Utils.h
@@ -24,12 +24,11 @@
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
-#include <nnapi/hal/HandleError.h>
 
 namespace aidl::android::hardware::neuralnetworks::utils {
 
 constexpr auto kDefaultPriority = Priority::MEDIUM;
-constexpr auto kVersion = nn::Version::ANDROID_S;
+constexpr auto kVersion = nn::Version::FEATURE_LEVEL_6;
 
 template <typename Type>
 nn::Result<void> validate(const Type& halObject) {
@@ -75,6 +74,13 @@
     for (const auto status = handleTransportError(ret); !status.ok();) \
     return NN_ERROR(status.error().code) << status.error().message << ": "
 
+#define HANDLE_STATUS_AIDL(status)                                                            \
+    if (const ::android::nn::ErrorStatus canonical = ::android::nn::convert(status).value_or( \
+                ::android::nn::ErrorStatus::GENERAL_FAILURE);                                 \
+        canonical == ::android::nn::ErrorStatus::NONE) {                                      \
+    } else                                                                                    \
+        return NN_ERROR(canonical)
+
 }  // namespace aidl::android::hardware::neuralnetworks::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_H
diff --git a/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ValidateHal.h b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ValidateHal.h
new file mode 100644
index 0000000..62aba31
--- /dev/null
+++ b/neuralnetworks/aidl/utils/include/nnapi/hal/aidl/ValidateHal.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_VALIDATE_HAL_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_VALIDATE_HAL_H
+
+#include "nnapi/hal/aidl/HalInterfaces.h"
+
+#include <memory>
+#include <set>
+#include <tuple>
+#include <vector>
+
+#include <nnapi/TypeUtils.h>
+#include <nnapi/Validation.h>
+
+namespace android {
+namespace nn {
+
+using AidlHalPreparedModelRole = std::tuple<const aidl_hal::IPreparedModel*, IOType, uint32_t>;
+
+bool validateMemoryDesc(
+        const aidl_hal::BufferDesc& desc,
+        const std::vector<std::shared_ptr<aidl_hal::IPreparedModel>>& preparedModels,
+        const std::vector<aidl_hal::BufferRole>& inputRoles,
+        const std::vector<aidl_hal::BufferRole>& outputRoles,
+        std::function<const aidl_hal::Model*(const std::shared_ptr<aidl_hal::IPreparedModel>&)>
+                getModel,
+        std::set<AidlHalPreparedModelRole>* preparedModelRoles, aidl_hal::Operand* combinedOperand);
+
+}  // namespace nn
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_AIDL_UTILS_VALIDATE_HAL_H
diff --git a/neuralnetworks/aidl/utils/src/Buffer.cpp b/neuralnetworks/aidl/utils/src/Buffer.cpp
index c729a68..9af3c9e 100644
--- a/neuralnetworks/aidl/utils/src/Buffer.cpp
+++ b/neuralnetworks/aidl/utils/src/Buffer.cpp
@@ -22,7 +22,6 @@
 
 #include "Conversions.h"
 #include "Utils.h"
-#include "nnapi/hal/aidl/Conversions.h"
 
 #include <memory>
 #include <utility>
diff --git a/neuralnetworks/aidl/utils/src/BufferTracker.cpp b/neuralnetworks/aidl/utils/src/BufferTracker.cpp
new file mode 100644
index 0000000..94dc891
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/BufferTracker.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2020 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 "BufferTracker.h"
+
+#include "HalInterfaces.h"
+
+#include <android-base/macros.h>
+#include <nnapi/TypeUtils.h>
+
+#include <memory>
+#include <mutex>
+#include <set>
+#include <stack>
+#include <utility>
+#include <vector>
+
+namespace android::nn {
+
+std::shared_ptr<AidlManagedBuffer> AidlManagedBuffer::create(
+        uint32_t size, std::set<AidlHalPreparedModelRole> roles, const Operand& operand) {
+    std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[size]);
+    if (buffer == nullptr) {
+        return nullptr;
+    }
+    if (isExtension(operand.type)) {
+        LOG(ERROR) << "AidlManagedBuffer cannot handle extension operands.";
+        return nullptr;
+    }
+    return std::make_shared<AidlManagedBuffer>(std::move(buffer), size, std::move(roles), operand);
+}
+
+AidlManagedBuffer::AidlManagedBuffer(std::unique_ptr<uint8_t[]> buffer, uint32_t size,
+                                     std::set<AidlHalPreparedModelRole> roles,
+                                     const Operand& operand)
+    : kBuffer(std::move(buffer)),
+      kSize(size),
+      kRoles(std::move(roles)),
+      kOperandType(operand.type),
+      kInitialDimensions(operand.dimensions),
+      mUpdatedDimensions(operand.dimensions) {
+    CHECK(!isExtension(kOperandType));
+}
+
+ErrorStatus AidlManagedBuffer::validateRequest(
+        uint32_t poolIndex, const Request& request,
+        const aidl_hal::IPreparedModel* preparedModel) const {
+    CHECK_LT(poolIndex, request.pools.size());
+    CHECK(std::holds_alternative<Request::MemoryDomainToken>(request.pools[poolIndex]));
+    std::lock_guard<std::mutex> guard(mMutex);
+
+    bool usedAsInput = false, usedAsOutput = false;
+    for (uint32_t i = 0; i < request.inputs.size(); i++) {
+        if (request.inputs[i].lifetime != Request::Argument::LifeTime::POOL) continue;
+        if (request.inputs[i].location.poolIndex != poolIndex) continue;
+        // Validate if the input role is specified during allocation.
+        if (kRoles.count({preparedModel, IOType::INPUT, i}) == 0) {
+            LOG(ERROR) << "AidlManagedBuffer::validateRequest -- invalid buffer role.";
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        if (!mInitialized) {
+            LOG(ERROR)
+                    << "AidlManagedBuffer::validateRequest -- using uninitialized buffer as input "
+                       "request.";
+            return ErrorStatus::GENERAL_FAILURE;
+        }
+        auto combined = combineDimensions(mUpdatedDimensions, request.inputs[i].dimensions);
+        if (!combined.has_value()) {
+            LOG(ERROR) << "AidlManagedBuffer::validateRequest -- incompatible dimensions ("
+                       << toString(mUpdatedDimensions) << " vs "
+                       << toString(request.inputs[i].dimensions) << ")";
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        usedAsInput = true;
+    }
+    for (uint32_t i = 0; i < request.outputs.size(); i++) {
+        if (request.outputs[i].lifetime != Request::Argument::LifeTime::POOL) continue;
+        if (request.outputs[i].location.poolIndex != poolIndex) continue;
+        if (usedAsInput || usedAsOutput) {
+            LOG(ERROR) << "AidlManagedBuffer::validateRequest -- using the same device memory for "
+                          "input/output or multiple outputs";
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        // Validate if the output role is specified during allocation.
+        if (kRoles.count({preparedModel, IOType::OUTPUT, i}) == 0) {
+            LOG(ERROR) << "AidlManagedBuffer::validateRequest -- invalid buffer role.";
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        auto combined = combineDimensions(kInitialDimensions, request.outputs[i].dimensions);
+        if (!combined.has_value()) {
+            LOG(ERROR) << "AidlManagedBuffer::validateRequest -- incompatible dimensions ("
+                       << toString(kInitialDimensions) << " vs "
+                       << toString(request.outputs[i].dimensions) << ")";
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        usedAsOutput = true;
+    }
+    return ErrorStatus::NONE;
+}
+
+ErrorStatus AidlManagedBuffer::validateCopyFrom(const std::vector<uint32_t>& dimensions,
+                                                uint32_t size) const {
+    if (size != kSize) {
+        LOG(ERROR) << "AidlManagedBuffer::validateCopyFrom -- invalid memory size: " << kSize
+                   << " vs " << size;
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+
+    if (isNonExtensionScalar(kOperandType)) {
+        if (!dimensions.empty()) {
+            LOG(ERROR) << "AidlManagedBuffer::validateCopyFrom -- invalid dimensions for scalar "
+                          "operand: "
+                       << toString(dimensions);
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+        return ErrorStatus::NONE;
+    }
+
+    if (dimensions.empty()) {
+        if (tensorHasUnspecifiedDimensions(kOperandType, kInitialDimensions)) {
+            LOG(ERROR) << "AidlManagedBuffer::validateCopyFrom -- the initial dimensions are not "
+                          "fully "
+                          "specified and no dimension update is provided: "
+                       << toString(kInitialDimensions);
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+    } else {
+        if (tensorHasUnspecifiedDimensions(kOperandType, dimensions)) {
+            LOG(ERROR) << "AidlManagedBuffer::validateCopyFrom -- the updated dimensions are not "
+                          "fully "
+                          "specified: "
+                       << toString(dimensions);
+            return ErrorStatus::INVALID_ARGUMENT;
+        }
+    }
+
+    const auto combined = combineDimensions(kInitialDimensions, dimensions);
+    if (!combined.has_value()) {
+        LOG(ERROR) << "AidlManagedBuffer::validateCopyFrom -- incompatible dimensions ("
+                   << toString(kInitialDimensions) << " vs " << toString(dimensions) << ")";
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+    return ErrorStatus::NONE;
+}
+
+ErrorStatus AidlManagedBuffer::validateCopyTo(uint32_t size) const {
+    if (size != kSize) {
+        LOG(ERROR) << "AidlManagedBuffer::validateCopyTo -- invalid memory size: " << kSize
+                   << " vs " << size;
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+    std::lock_guard<std::mutex> guard(mMutex);
+    if (!mInitialized) {
+        LOG(ERROR) << "AidlManagedBuffer::validateCopyTo -- using uninitialized buffer as source.";
+        return ErrorStatus::GENERAL_FAILURE;
+    }
+    return ErrorStatus::NONE;
+}
+
+bool AidlManagedBuffer::updateDimensions(const std::vector<uint32_t>& dimensions) {
+    auto combined = combineDimensions(kInitialDimensions, dimensions);
+    if (!combined.has_value()) {
+        LOG(ERROR) << "AidlManagedBuffer::updateDimensions -- incompatible dimensions ("
+                   << toString(kInitialDimensions) << " vs " << toString(dimensions) << ")";
+        return false;
+    }
+    std::lock_guard<std::mutex> guard(mMutex);
+    mUpdatedDimensions = std::move(combined).value();
+    return true;
+}
+
+void AidlManagedBuffer::setInitialized(bool initialized) {
+    std::lock_guard<std::mutex> guard(mMutex);
+    mInitialized = initialized;
+}
+
+std::unique_ptr<AidlBufferTracker::Token> AidlBufferTracker::add(
+        std::shared_ptr<AidlManagedBuffer> buffer) {
+    if (buffer == nullptr) {
+        return nullptr;
+    }
+    std::lock_guard<std::mutex> guard(mMutex);
+    uint32_t token = 0;
+    if (mFreeTokens.empty()) {
+        token = mTokenToBuffers.size();
+        mTokenToBuffers.push_back(std::move(buffer));
+    } else {
+        token = mFreeTokens.top();
+        mFreeTokens.pop();
+        mTokenToBuffers[token] = std::move(buffer);
+    }
+    VLOG(MEMORY) << "AidlBufferTracker::add -- new token = " << token;
+    return std::make_unique<Token>(token, shared_from_this());
+}
+
+std::shared_ptr<AidlManagedBuffer> AidlBufferTracker::get(uint32_t token) const {
+    std::lock_guard<std::mutex> guard(mMutex);
+    if (mTokenToBuffers.size() <= token || mTokenToBuffers[token] == nullptr) {
+        LOG(ERROR) << "AidlBufferTracker::get -- unknown token " << token;
+        return nullptr;
+    }
+    return mTokenToBuffers[token];
+}
+
+void AidlBufferTracker::free(uint32_t token) {
+    std::lock_guard<std::mutex> guard(mMutex);
+    CHECK_LT(token, mTokenToBuffers.size());
+    CHECK(mTokenToBuffers[token] != nullptr);
+    VLOG(MEMORY) << "AidlBufferTracker::free -- release token = " << token;
+    mTokenToBuffers[token] = nullptr;
+    mFreeTokens.push(token);
+}
+
+}  // namespace android::nn
diff --git a/neuralnetworks/aidl/utils/src/Burst.cpp b/neuralnetworks/aidl/utils/src/Burst.cpp
index c59c10b..fb00b26 100644
--- a/neuralnetworks/aidl/utils/src/Burst.cpp
+++ b/neuralnetworks/aidl/utils/src/Burst.cpp
@@ -26,7 +26,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <memory>
 #include <mutex>
diff --git a/neuralnetworks/aidl/utils/src/Callbacks.cpp b/neuralnetworks/aidl/utils/src/Callbacks.cpp
index 8055665..a321477 100644
--- a/neuralnetworks/aidl/utils/src/Callbacks.cpp
+++ b/neuralnetworks/aidl/utils/src/Callbacks.cpp
@@ -38,7 +38,7 @@
 // nn::Version::ANDROID_S. On failure, this function returns with the appropriate nn::GeneralError.
 nn::GeneralResult<nn::SharedPreparedModel> prepareModelCallback(
         ErrorStatus status, const std::shared_ptr<IPreparedModel>& preparedModel) {
-    HANDLE_HAL_STATUS(status) << "model preparation failed with " << toString(status);
+    HANDLE_STATUS_AIDL(status) << "model preparation failed with " << toString(status);
     return NN_TRY(PreparedModel::create(preparedModel));
 }
 
diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp
index ddff3f2..45628c8 100644
--- a/neuralnetworks/aidl/utils/src/Conversions.cpp
+++ b/neuralnetworks/aidl/utils/src/Conversions.cpp
@@ -34,7 +34,6 @@
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <algorithm>
 #include <chrono>
diff --git a/neuralnetworks/aidl/utils/src/Device.cpp b/neuralnetworks/aidl/utils/src/Device.cpp
index e80de0b..5b7ec4e 100644
--- a/neuralnetworks/aidl/utils/src/Device.cpp
+++ b/neuralnetworks/aidl/utils/src/Device.cpp
@@ -125,7 +125,7 @@
 }  // namespace
 
 nn::GeneralResult<std::shared_ptr<const Device>> Device::create(
-        std::string name, std::shared_ptr<aidl_hal::IDevice> device) {
+        std::string name, std::shared_ptr<aidl_hal::IDevice> device, nn::Version featureLevel) {
     if (name.empty()) {
         return NN_ERROR(nn::ErrorStatus::INVALID_ARGUMENT)
                << "aidl_hal::utils::Device::create must have non-empty name";
@@ -143,18 +143,19 @@
 
     auto deathHandler = NN_TRY(DeathHandler::create(device));
     return std::make_shared<const Device>(
-            PrivateConstructorTag{}, std::move(name), std::move(versionString), deviceType,
-            std::move(extensions), std::move(capabilities), numberOfCacheFilesNeeded,
+            PrivateConstructorTag{}, std::move(name), std::move(versionString), featureLevel,
+            deviceType, std::move(extensions), std::move(capabilities), numberOfCacheFilesNeeded,
             std::move(device), std::move(deathHandler));
 }
 
 Device::Device(PrivateConstructorTag /*tag*/, std::string name, std::string versionString,
-               nn::DeviceType deviceType, std::vector<nn::Extension> extensions,
-               nn::Capabilities capabilities,
+               nn::Version featureLevel, nn::DeviceType deviceType,
+               std::vector<nn::Extension> extensions, nn::Capabilities capabilities,
                std::pair<uint32_t, uint32_t> numberOfCacheFilesNeeded,
                std::shared_ptr<aidl_hal::IDevice> device, DeathHandler deathHandler)
     : kName(std::move(name)),
       kVersionString(std::move(versionString)),
+      kFeatureLevel(featureLevel),
       kDeviceType(deviceType),
       kExtensions(std::move(extensions)),
       kCapabilities(std::move(capabilities)),
@@ -171,7 +172,7 @@
 }
 
 nn::Version Device::getFeatureLevel() const {
-    return nn::Version::ANDROID_S;
+    return kFeatureLevel;
 }
 
 nn::DeviceType Device::getType() const {
diff --git a/neuralnetworks/aidl/utils/src/Execution.cpp b/neuralnetworks/aidl/utils/src/Execution.cpp
index 13d4f32..94edd90 100644
--- a/neuralnetworks/aidl/utils/src/Execution.cpp
+++ b/neuralnetworks/aidl/utils/src/Execution.cpp
@@ -25,7 +25,6 @@
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <memory>
 #include <utility>
diff --git a/neuralnetworks/aidl/utils/src/HalUtils.cpp b/neuralnetworks/aidl/utils/src/HalUtils.cpp
new file mode 100644
index 0000000..64b6259
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/HalUtils.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+// This file contains pre-canonical-types utility code and includes HAL
+// utilities. LegacyUtils.h is the subset of these utilities that do not touch
+// HAL.
+
+#include "HalUtils.h"
+
+#include "HalInterfaces.h"
+
+#include <android-base/logging.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <vector>
+
+namespace android::nn {
+
+std::vector<aidl_hal::OperandPerformance> nonExtensionOperandPerformance(
+        aidl_hal::PerformanceInfo perf) {
+    static constexpr ndk::enum_range<aidl_hal::OperandType> kOperandTypeRange;
+    std::vector<aidl_hal::OperandPerformance> ret;
+    ret.reserve(std::distance(kOperandTypeRange.begin(), kOperandTypeRange.end()));
+    for (aidl_hal::OperandType type : kOperandTypeRange) {
+        if (type != aidl_hal::OperandType::SUBGRAPH) {
+            ret.push_back(aidl_hal::OperandPerformance{type, perf});
+        }
+    }
+    std::sort(ret.begin(), ret.end(),
+              [](const aidl_hal::OperandPerformance& a, const aidl_hal::OperandPerformance& b) {
+                  return a.type < b.type;
+              });
+
+    return ret;
+}
+
+void update(std::vector<aidl_hal::OperandPerformance>* operandPerformance,
+            aidl_hal::OperandType type, aidl_hal::PerformanceInfo perf) {
+    CHECK(operandPerformance != nullptr);
+    const auto it = std::lower_bound(operandPerformance->begin(), operandPerformance->end(), type,
+                                     [](const aidl_hal::OperandPerformance& perf,
+                                        aidl_hal::OperandType type) { return perf.type < type; });
+    CHECK(it != operandPerformance->end())
+            << toString(type) << " not in operand performance vector";
+    it->info = perf;
+}
+
+bool isExtensionOperandType(aidl_hal::OperandType type) {
+    return isExtension(convert(type).value());
+}
+
+bool isNonExtensionScalar(aidl_hal::OperandType type) {
+    return isNonExtensionScalar(convert(type).value());
+}
+
+}  // namespace android::nn
diff --git a/neuralnetworks/aidl/utils/src/PreparedModel.cpp b/neuralnetworks/aidl/utils/src/PreparedModel.cpp
index 0769016..f25c2c8 100644
--- a/neuralnetworks/aidl/utils/src/PreparedModel.cpp
+++ b/neuralnetworks/aidl/utils/src/PreparedModel.cpp
@@ -30,7 +30,6 @@
 #include <nnapi/TypeUtils.h>
 #include <nnapi/Types.h>
 #include <nnapi/hal/CommonUtils.h>
-#include <nnapi/hal/HandleError.h>
 
 #include <memory>
 #include <tuple>
@@ -51,7 +50,7 @@
 nn::GeneralResult<std::pair<nn::Timing, nn::Timing>> convertFencedExecutionResults(
         ErrorStatus status, const aidl_hal::Timing& timingLaunched,
         const aidl_hal::Timing& timingFenced) {
-    HANDLE_HAL_STATUS(status) << "fenced execution callback info failed with " << toString(status);
+    HANDLE_STATUS_AIDL(status) << "fenced execution callback info failed with " << toString(status);
     return std::make_pair(NN_TRY(nn::convert(timingLaunched)), NN_TRY(nn::convert(timingFenced)));
 }
 
diff --git a/neuralnetworks/aidl/utils/src/ProtectCallback.cpp b/neuralnetworks/aidl/utils/src/ProtectCallback.cpp
index 124641c..54a673c 100644
--- a/neuralnetworks/aidl/utils/src/ProtectCallback.cpp
+++ b/neuralnetworks/aidl/utils/src/ProtectCallback.cpp
@@ -22,7 +22,6 @@
 #include <android/binder_auto_utils.h>
 #include <android/binder_interface_utils.h>
 #include <nnapi/Result.h>
-#include <nnapi/hal/ProtectCallback.h>
 
 #include <algorithm>
 #include <functional>
@@ -37,7 +36,7 @@
 void DeathMonitor::serviceDied() {
     std::lock_guard guard(mMutex);
     std::for_each(mObjects.begin(), mObjects.end(),
-                  [](hal::utils::IProtectedCallback* killable) { killable->notifyAsDeadObject(); });
+                  [](IProtectedCallback* killable) { killable->notifyAsDeadObject(); });
 }
 
 void DeathMonitor::serviceDied(void* cookie) {
@@ -45,13 +44,13 @@
     deathMonitor->serviceDied();
 }
 
-void DeathMonitor::add(hal::utils::IProtectedCallback* killable) const {
+void DeathMonitor::add(IProtectedCallback* killable) const {
     CHECK(killable != nullptr);
     std::lock_guard guard(mMutex);
     mObjects.push_back(killable);
 }
 
-void DeathMonitor::remove(hal::utils::IProtectedCallback* killable) const {
+void DeathMonitor::remove(IProtectedCallback* killable) const {
     CHECK(killable != nullptr);
     std::lock_guard guard(mMutex);
     const auto removedIter = std::remove(mObjects.begin(), mObjects.end(), killable);
@@ -102,7 +101,7 @@
 }
 
 [[nodiscard]] ::android::base::ScopeGuard<DeathHandler::Cleanup> DeathHandler::protectCallback(
-        hal::utils::IProtectedCallback* killable) const {
+        IProtectedCallback* killable) const {
     CHECK(killable != nullptr);
     kDeathMonitor->add(killable);
     return ::android::base::make_scope_guard(
diff --git a/neuralnetworks/aidl/utils/src/Service.cpp b/neuralnetworks/aidl/utils/src/Service.cpp
index ac182a2..01772ee 100644
--- a/neuralnetworks/aidl/utils/src/Service.cpp
+++ b/neuralnetworks/aidl/utils/src/Service.cpp
@@ -17,6 +17,7 @@
 #include "Service.h"
 
 #include <AndroidVersionUtil.h>
+#include <aidl/android/hardware/neuralnetworks/IDevice.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
@@ -28,8 +29,33 @@
 #include <string>
 
 #include "Device.h"
+#include "Utils.h"
 
 namespace aidl::android::hardware::neuralnetworks::utils {
+namespace {
+
+// Map the AIDL version of an IDevice to NNAPI canonical feature level.
+nn::GeneralResult<nn::Version> getAidlServiceFeatureLevel(IDevice* service) {
+    CHECK(service != nullptr);
+    int aidlVersion;
+    const auto ret = service->getInterfaceVersion(&aidlVersion);
+    HANDLE_ASTATUS(ret) << "getInterfaceVersion failed";
+
+    // For service AIDL versions greater than or equal to the AIDL library version that the runtime
+    // was built against, clamp it to the runtime AIDL library version.
+    aidlVersion = std::min(aidlVersion, IDevice::version);
+
+    // Map stable AIDL versions to canonical versions.
+    switch (aidlVersion) {
+        case 1:
+            return nn::Version::ANDROID_S;
+        case 2:
+            return nn::Version::FEATURE_LEVEL_6;
+    }
+    return NN_ERROR() << "Unknown AIDL service version: " << aidlVersion;
+}
+
+}  // namespace
 
 nn::GeneralResult<nn::SharedDevice> getDevice(const std::string& instanceName) {
     auto fullName = std::string(IDevice::descriptor) + "/" + instanceName;
@@ -55,7 +81,8 @@
                    << " returned nullptr";
         }
         ABinderProcess_startThreadPool();
-        return Device::create(instanceName, std::move(service));
+        const auto featureLevel = NN_TRY(getAidlServiceFeatureLevel(service.get()));
+        return Device::create(instanceName, std::move(service), featureLevel);
     };
 
     return hal::utils::ResilientDevice::create(std::move(makeDevice));
diff --git a/neuralnetworks/aidl/utils/src/ValidateHal.cpp b/neuralnetworks/aidl/utils/src/ValidateHal.cpp
new file mode 100644
index 0000000..a56de21
--- /dev/null
+++ b/neuralnetworks/aidl/utils/src/ValidateHal.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ValidateHal"
+
+#include "ValidateHal.h"
+
+#include "HalUtils.h"
+
+#include <android-base/logging.h>
+#include <nnapi/TypeUtils.h>
+#include <nnapi/hal/aidl/Conversions.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+namespace android {
+namespace nn {
+
+bool validateMemoryDesc(
+        const aidl_hal::BufferDesc& desc,
+        const std::vector<std::shared_ptr<aidl_hal::IPreparedModel>>& preparedModels,
+        const std::vector<aidl_hal::BufferRole>& inputRoles,
+        const std::vector<aidl_hal::BufferRole>& outputRoles,
+        std::function<const aidl_hal::Model*(const std::shared_ptr<aidl_hal::IPreparedModel>&)>
+                getModel,
+        std::set<AidlHalPreparedModelRole>* preparedModelRoles,
+        aidl_hal::Operand* combinedOperand) {
+    NN_RET_CHECK(!preparedModels.empty());
+    NN_RET_CHECK(!inputRoles.empty() || !outputRoles.empty());
+
+    std::set<AidlHalPreparedModelRole> roles;
+    std::vector<aidl_hal::Operand> operands;
+    operands.reserve(inputRoles.size() + outputRoles.size());
+    for (const auto& role : inputRoles) {
+        NN_RET_CHECK_GE(role.modelIndex, 0);
+        NN_RET_CHECK_LT(static_cast<size_t>(role.modelIndex), preparedModels.size());
+        const auto& preparedModel = preparedModels[role.modelIndex];
+        NN_RET_CHECK(preparedModel != nullptr);
+        const auto* model = getModel(preparedModel);
+        NN_RET_CHECK(model != nullptr);
+        const auto& inputIndexes = model->main.inputIndexes;
+        NN_RET_CHECK_GE(role.ioIndex, 0);
+        NN_RET_CHECK_LT(static_cast<size_t>(role.ioIndex), inputIndexes.size());
+        NN_RET_CHECK_GT(role.probability, 0.0f);
+        NN_RET_CHECK_LE(role.probability, 1.0f);
+        const auto [it, success] = roles.emplace(preparedModel.get(), IOType::INPUT, role.ioIndex);
+        NN_RET_CHECK(success);
+        operands.push_back(model->main.operands[inputIndexes[role.ioIndex]]);
+    }
+    for (const auto& role : outputRoles) {
+        NN_RET_CHECK_GE(role.modelIndex, 0);
+        NN_RET_CHECK_LT(static_cast<size_t>(role.modelIndex), preparedModels.size());
+        const auto& preparedModel = preparedModels[role.modelIndex];
+        NN_RET_CHECK(preparedModel != nullptr);
+        const auto* model = getModel(preparedModel);
+        NN_RET_CHECK(model != nullptr);
+        const auto& outputIndexes = model->main.outputIndexes;
+        NN_RET_CHECK_GE(role.ioIndex, 0);
+        NN_RET_CHECK_LT(static_cast<size_t>(role.ioIndex), outputIndexes.size());
+        NN_RET_CHECK_GT(role.probability, 0.0f);
+        NN_RET_CHECK_LE(role.probability, 1.0f);
+        const auto [it, success] = roles.emplace(preparedModel.get(), IOType::OUTPUT, role.ioIndex);
+        NN_RET_CHECK(success);
+        operands.push_back(model->main.operands[outputIndexes[role.ioIndex]]);
+    }
+
+    CHECK(!operands.empty());
+    const auto opType = operands[0].type;
+    const auto canonicalOperandType = convert(opType);
+    NN_RET_CHECK(canonicalOperandType.has_value()) << canonicalOperandType.error().message;
+    const bool isExtensionOperand = isExtension(canonicalOperandType.value());
+
+    auto maybeDimensions = toUnsigned(desc.dimensions);
+    NN_RET_CHECK(maybeDimensions.has_value()) << maybeDimensions.error().message;
+    std::vector<uint32_t> dimensions = std::move(maybeDimensions).value();
+
+    for (const auto& operand : operands) {
+        NN_RET_CHECK(operand.type == operands[0].type)
+                << toString(operand.type) << " vs " << toString(operands[0].type);
+        NN_RET_CHECK_EQ(operand.scale, operands[0].scale);
+        NN_RET_CHECK_EQ(operand.zeroPoint, operands[0].zeroPoint);
+        // NOTE: validateMemoryDesc cannot validate extra parameters for extension operand type.
+        if (!isExtensionOperand) {
+            const auto& lhsExtraParams = operand.extraParams;
+            const auto& rhsExtraParams = operands[0].extraParams;
+            NN_RET_CHECK(lhsExtraParams == rhsExtraParams)
+                    << (lhsExtraParams.has_value() ? lhsExtraParams.value().toString()
+                                                   : "std::nullopt")
+                    << " vs "
+                    << (rhsExtraParams.has_value() ? rhsExtraParams.value().toString()
+                                                   : "std::nullopt");
+        }
+        const auto maybeRhsDimensions = toUnsigned(operand.dimensions);
+        NN_RET_CHECK(maybeRhsDimensions.has_value()) << maybeRhsDimensions.error().message;
+        const auto combined = combineDimensions(dimensions, maybeRhsDimensions.value());
+        NN_RET_CHECK(combined.has_value());
+        dimensions = combined.value();
+    }
+
+    // NOTE: validateMemoryDesc cannot validate scalar dimensions with extension operand type.
+    if (!isExtensionOperand) {
+        NN_RET_CHECK(!isNonExtensionScalar(opType) || dimensions.empty())
+                << "invalid dimensions with scalar operand type.";
+    }
+
+    if (preparedModelRoles != nullptr) {
+        *preparedModelRoles = std::move(roles);
+    }
+    if (combinedOperand != nullptr) {
+        *combinedOperand = operands[0];
+        // No need to check that values fit int32_t here, since the original values are obtained
+        // from int32_t.
+        combinedOperand->dimensions = aidl_hal::utils::toSigned(dimensions).value();
+    }
+    return true;
+}
+
+}  // namespace nn
+}  // namespace android
diff --git a/neuralnetworks/aidl/utils/test/DeviceTest.cpp b/neuralnetworks/aidl/utils/test/DeviceTest.cpp
index f121aca..79abe1b 100644
--- a/neuralnetworks/aidl/utils/test/DeviceTest.cpp
+++ b/neuralnetworks/aidl/utils/test/DeviceTest.cpp
@@ -146,28 +146,45 @@
     return ndk::ScopedAStatus::fromStatus(STATUS_DEAD_OBJECT);
 };
 
+class DeviceTest : public ::testing::TestWithParam<nn::Version> {
+  protected:
+    const nn::Version kVersion = GetParam();
+};
+
+std::string printDeviceTest(const testing::TestParamInfo<nn::Version>& info) {
+    switch (info.param) {
+        case nn::Version::ANDROID_S:
+            return "v1";
+        case nn::Version::FEATURE_LEVEL_6:
+            return "v2";
+        default:
+            LOG(FATAL) << "Invalid AIDL version: " << info.param;
+            return "invalid";
+    }
+}
+
 }  // namespace
 
-TEST(DeviceTest, invalidName) {
+TEST_P(DeviceTest, invalidName) {
     // run test
     const auto device = MockDevice::create();
-    const auto result = Device::create(kInvalidName, device);
+    const auto result = Device::create(kInvalidName, device, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
 }
 
-TEST(DeviceTest, invalidDevice) {
+TEST_P(DeviceTest, invalidDevice) {
     // run test
-    const auto result = Device::create(kName, kInvalidDevice);
+    const auto result = Device::create(kName, kInvalidDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::INVALID_ARGUMENT);
 }
 
-TEST(DeviceTest, getVersionStringError) {
+TEST_P(DeviceTest, getVersionStringError) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getVersionString(_))
@@ -175,14 +192,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getVersionStringTransportFailure) {
+TEST_P(DeviceTest, getVersionStringTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getVersionString(_))
@@ -190,14 +207,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getVersionStringDeadObject) {
+TEST_P(DeviceTest, getVersionStringDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getVersionString(_))
@@ -205,27 +222,27 @@
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, getTypeError) {
+TEST_P(DeviceTest, getTypeError) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getType(_)).Times(1).WillOnce(InvokeWithoutArgs(makeGeneralFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getTypeTransportFailure) {
+TEST_P(DeviceTest, getTypeTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getType(_))
@@ -233,14 +250,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getTypeDeadObject) {
+TEST_P(DeviceTest, getTypeDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getType(_))
@@ -248,14 +265,14 @@
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, getSupportedExtensionsError) {
+TEST_P(DeviceTest, getSupportedExtensionsError) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
@@ -263,14 +280,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getSupportedExtensionsTransportFailure) {
+TEST_P(DeviceTest, getSupportedExtensionsTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
@@ -278,14 +295,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getSupportedExtensionsDeadObject) {
+TEST_P(DeviceTest, getSupportedExtensionsDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getSupportedExtensions(_))
@@ -293,20 +310,20 @@
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, getNumberOfCacheFilesNeeded) {
+TEST_P(DeviceTest, getNumberOfCacheFilesNeeded) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_TRUE(result.has_value());
@@ -315,7 +332,7 @@
     EXPECT_EQ(result.value()->getNumberOfCacheFilesNeeded(), kNumberOfCacheFilesPair);
 }
 
-TEST(DeviceTest, getNumberOfCacheFilesNeededError) {
+TEST_P(DeviceTest, getNumberOfCacheFilesNeededError) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
@@ -323,14 +340,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
+TEST_P(DeviceTest, dataCacheFilesExceedsSpecifiedMax) {
     // setup test
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
@@ -341,14 +358,14 @@
                             InvokeWithoutArgs(makeStatusOk)));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
+TEST_P(DeviceTest, modelCacheFilesExceedsSpecifiedMax) {
     // setup test
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
@@ -359,14 +376,14 @@
                             InvokeWithoutArgs(makeStatusOk)));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
+TEST_P(DeviceTest, getNumberOfCacheFilesNeededTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
@@ -374,14 +391,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
+TEST_P(DeviceTest, getNumberOfCacheFilesNeededDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_))
@@ -389,14 +406,14 @@
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, getCapabilitiesError) {
+TEST_P(DeviceTest, getCapabilitiesError) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getCapabilities(_))
@@ -404,14 +421,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getCapabilitiesTransportFailure) {
+TEST_P(DeviceTest, getCapabilitiesTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getCapabilities(_))
@@ -419,14 +436,14 @@
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getCapabilitiesDeadObject) {
+TEST_P(DeviceTest, getCapabilitiesDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getCapabilities(_))
@@ -434,17 +451,17 @@
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
 
     // run test
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
 
     // verify result
     ASSERT_FALSE(result.has_value());
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, getName) {
+TEST_P(DeviceTest, getName) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
 
     // run test
     const auto& name = device->getName();
@@ -453,19 +470,19 @@
     EXPECT_EQ(name, kName);
 }
 
-TEST(DeviceTest, getFeatureLevel) {
+TEST_P(DeviceTest, getFeatureLevel) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
 
     // run test
     const auto featureLevel = device->getFeatureLevel();
 
     // verify result
-    EXPECT_EQ(featureLevel, nn::Version::ANDROID_S);
+    EXPECT_EQ(featureLevel, kVersion);
 }
 
-TEST(DeviceTest, getCachedData) {
+TEST_P(DeviceTest, getCachedData) {
     // setup call
     const auto mockDevice = createMockDevice();
     EXPECT_CALL(*mockDevice, getVersionString(_)).Times(1);
@@ -474,7 +491,7 @@
     EXPECT_CALL(*mockDevice, getNumberOfCacheFilesNeeded(_)).Times(1);
     EXPECT_CALL(*mockDevice, getCapabilities(_)).Times(1);
 
-    const auto result = Device::create(kName, mockDevice);
+    const auto result = Device::create(kName, mockDevice, kVersion);
     ASSERT_TRUE(result.has_value())
             << "Failed with " << result.error().code << ": " << result.error().message;
     const auto& device = result.value();
@@ -487,10 +504,10 @@
     EXPECT_EQ(device->getCapabilities(), device->getCapabilities());
 }
 
-TEST(DeviceTest, getSupportedOperations) {
+TEST_P(DeviceTest, getSupportedOperations) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
             .Times(1)
             .WillOnce(DoAll(
@@ -508,10 +525,10 @@
     EXPECT_THAT(supportedOperations, Each(testing::IsTrue()));
 }
 
-TEST(DeviceTest, getSupportedOperationsError) {
+TEST_P(DeviceTest, getSupportedOperationsError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
@@ -524,10 +541,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getSupportedOperationsTransportFailure) {
+TEST_P(DeviceTest, getSupportedOperationsTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -540,10 +557,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, getSupportedOperationsDeadObject) {
+TEST_P(DeviceTest, getSupportedOperationsDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, getSupportedOperations(_, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -556,10 +573,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, prepareModel) {
+TEST_P(DeviceTest, prepareModel) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     const auto mockPreparedModel = MockPreparedModel::create();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
@@ -576,10 +593,10 @@
     EXPECT_NE(result.value(), nullptr);
 }
 
-TEST(DeviceTest, prepareModelLaunchError) {
+TEST_P(DeviceTest, prepareModelLaunchError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
             .WillOnce(Invoke(makePreparedModelReturn(ErrorStatus::GENERAL_FAILURE,
@@ -594,10 +611,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelReturnError) {
+TEST_P(DeviceTest, prepareModelReturnError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
             .WillOnce(Invoke(makePreparedModelReturn(ErrorStatus::NONE,
@@ -612,10 +629,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelNullptrError) {
+TEST_P(DeviceTest, prepareModelNullptrError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
             .WillOnce(
@@ -630,10 +647,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelTransportFailure) {
+TEST_P(DeviceTest, prepareModelTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -647,10 +664,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelDeadObject) {
+TEST_P(DeviceTest, prepareModelDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModel(_, _, _, _, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -664,10 +681,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, prepareModelAsyncCrash) {
+TEST_P(DeviceTest, prepareModelAsyncCrash) {
     // setup test
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     const auto ret = [&device]() {
         DeathMonitor::serviceDied(device->getDeathMonitor());
         return ndk::ScopedAStatus::ok();
@@ -685,10 +702,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, prepareModelFromCache) {
+TEST_P(DeviceTest, prepareModelFromCache) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     const auto mockPreparedModel = MockPreparedModel::create();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
@@ -704,10 +721,10 @@
     EXPECT_NE(result.value(), nullptr);
 }
 
-TEST(DeviceTest, prepareModelFromCacheLaunchError) {
+TEST_P(DeviceTest, prepareModelFromCacheLaunchError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
             .WillOnce(Invoke(makePreparedModelFromCacheReturn(
@@ -721,10 +738,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelFromCacheReturnError) {
+TEST_P(DeviceTest, prepareModelFromCacheReturnError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
             .WillOnce(Invoke(makePreparedModelFromCacheReturn(
@@ -738,10 +755,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelFromCacheNullptrError) {
+TEST_P(DeviceTest, prepareModelFromCacheNullptrError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
             .WillOnce(Invoke(makePreparedModelFromCacheReturn(ErrorStatus::NONE, ErrorStatus::NONE,
@@ -755,10 +772,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelFromCacheTransportFailure) {
+TEST_P(DeviceTest, prepareModelFromCacheTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -771,10 +788,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, prepareModelFromCacheDeadObject) {
+TEST_P(DeviceTest, prepareModelFromCacheDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, prepareModelFromCache(_, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -787,10 +804,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, prepareModelFromCacheAsyncCrash) {
+TEST_P(DeviceTest, prepareModelFromCacheAsyncCrash) {
     // setup test
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     const auto ret = [&device]() {
         DeathMonitor::serviceDied(device->getDeathMonitor());
         return ndk::ScopedAStatus::ok();
@@ -807,10 +824,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
-TEST(DeviceTest, allocate) {
+TEST_P(DeviceTest, allocate) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     const auto mockBuffer = DeviceBuffer{.buffer = MockBuffer::create(), .token = 1};
     EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
             .Times(1)
@@ -825,10 +842,10 @@
     EXPECT_NE(result.value(), nullptr);
 }
 
-TEST(DeviceTest, allocateError) {
+TEST_P(DeviceTest, allocateError) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralFailure));
@@ -841,10 +858,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, allocateTransportFailure) {
+TEST_P(DeviceTest, allocateTransportFailure) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeGeneralTransportFailure));
@@ -857,10 +874,10 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::GENERAL_FAILURE);
 }
 
-TEST(DeviceTest, allocateDeadObject) {
+TEST_P(DeviceTest, allocateDeadObject) {
     // setup call
     const auto mockDevice = createMockDevice();
-    const auto device = Device::create(kName, mockDevice).value();
+    const auto device = Device::create(kName, mockDevice, kVersion).value();
     EXPECT_CALL(*mockDevice, allocate(_, _, _, _, _))
             .Times(1)
             .WillOnce(InvokeWithoutArgs(makeDeadObjectFailure));
@@ -873,4 +890,8 @@
     EXPECT_EQ(result.error().code, nn::ErrorStatus::DEAD_OBJECT);
 }
 
+INSTANTIATE_TEST_SUITE_P(TestDevice, DeviceTest,
+                         ::testing::Values(nn::Version::ANDROID_S, nn::Version::FEATURE_LEVEL_6),
+                         printDeviceTest);
+
 }  // namespace aidl::android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/aidl/vts/functional/Android.bp b/neuralnetworks/aidl/vts/functional/Android.bp
index f3404a9..a102fe0 100644
--- a/neuralnetworks/aidl/vts/functional/Android.bp
+++ b/neuralnetworks/aidl/vts/functional/Android.bp
@@ -26,6 +26,7 @@
 cc_test {
     name: "VtsHalNeuralnetworksTargetTest",
     defaults: [
+        "neuralnetworks_use_latest_utils_hal_aidl",
         "neuralnetworks_vts_functional_defaults",
         "use_libaidlvintf_gtest_helper_static",
     ],
@@ -49,20 +50,17 @@
         "libvndksupport",
     ],
     static_libs: [
-        "android.hardware.common-V2-ndk",
-        "android.hardware.graphics.common-V3-ndk",
-        "android.hardware.neuralnetworks-V2-ndk",
         "android.hidl.allocator@1.0",
         "android.hidl.memory@1.0",
         "libaidlcommonsupport",
         "libgmock",
         "libhidlmemory",
+        "libneuralnetworks_common",
         "libneuralnetworks_generated_test_harness",
-        "libneuralnetworks_utils",
         "libsync",
-        "neuralnetworks_utils_hal_aidl",
     ],
     whole_static_libs: [
+        "neuralnetworks_generated_AIDL_V2_example",
         "neuralnetworks_generated_V1_0_example",
         "neuralnetworks_generated_V1_1_example",
         "neuralnetworks_generated_V1_2_example",
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
index ac5b96a..f67fd34 100644
--- a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
@@ -907,6 +907,20 @@
     const bool deviceIsResponsive =
             ndk::ScopedAStatus::fromStatus(AIBinder_ping(kDevice->asBinder().get())).isOk();
     ASSERT_TRUE(deviceIsResponsive);
+    //  TODO(b/201260787): We should require old drivers to report the model as
+    //  unsupported instead of simply skipping the test.
+    SkipIfDriverOlderThanTestModel();
+}
+
+void GeneratedTestBase::SkipIfDriverOlderThanTestModel() {
+    int32_t deviceVersion;
+    ASSERT_TRUE(kDevice->getInterfaceVersion(&deviceVersion).isOk());
+    const int32_t modelVersion = kTestModel.getAidlVersionInt();
+    if (deviceVersion < modelVersion) {
+        GTEST_SKIP() << "Device interface version " << deviceVersion
+                     << " is older than test model's minimum supported HAL version " << modelVersion
+                     << ". Skipping test.";
+    }
 }
 
 std::vector<NamedModel> getNamedModels(const FilterFn& filter) {
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
index ad40f06..da74db9 100644
--- a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.h
@@ -34,6 +34,9 @@
     void SetUp() override;
     const std::shared_ptr<IDevice> kDevice = getData(std::get<NamedDevice>(GetParam()));
     const test_helper::TestModel& kTestModel = *getData(std::get<NamedModel>(GetParam()));
+
+  private:
+    void SkipIfDriverOlderThanTestModel();
 };
 
 using FilterFn = std::function<bool(const test_helper::TestModel&)>;
diff --git a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
index 1819699..cd5475c 100644
--- a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
+++ b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
@@ -28,9 +28,9 @@
 #include <Utils.h>
 #include <nnapi/SharedMemory.h>
 #include <nnapi/hal/aidl/Conversions.h>
+#include <nnapi/hal/aidl/HalInterfaces.h>
 #include <nnapi/hal/aidl/Utils.h>
 
-#include "AidlHalInterfaces.h"
 #include "Callbacks.h"
 #include "GeneratedTestHarness.h"
 #include "MemoryUtils.h"
diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
index 698c054..fdc7eff 100644
--- a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
@@ -1122,6 +1122,7 @@
     //   align_corners and half_pixel_centers parameters.
     // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional axis
     //   parameter.
+    // - PACK has at least 2 inputs, with the first element being INT32.
     switch (op.type) {
         case OperationType::CONCATENATION: {
             if (op.inputs.size() > 2 && input != op.inputs.size() - 1) {
@@ -1178,6 +1179,11 @@
                 return true;
             }
         } break;
+        case OperationType::PACK: {
+            if (op.inputs.size() > 2 && input != 0) {
+                return true;
+            }
+        } break;
         default:
             break;
     }
@@ -1315,8 +1321,8 @@
 
 void validateModel(const std::shared_ptr<IDevice>& device, const Model& model) {
     const auto numberOfConsumers =
-            nn::countNumberOfConsumers(model.main.operands.size(),
-                                       nn::unvalidatedConvert(model.main.operations).value())
+            countNumberOfConsumers(model.main.operands.size(),
+                                   nn::unvalidatedConvert(model.main.operations).value())
                     .value();
     mutateExecutionOrderTest(device, model, numberOfConsumers);
     mutateOperandTypeTest(device, model);
diff --git a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h b/neuralnetworks/utils/adapter/include/nnapi/hal/Burst.h
similarity index 65%
rename from neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
rename to neuralnetworks/utils/adapter/include/nnapi/hal/Burst.h
index f7926f5..a3aa706 100644
--- a/neuralnetworks/1.2/utils/include/nnapi/hal/1.2/ExecutionBurstServer.h
+++ b/neuralnetworks/utils/adapter/include/nnapi/hal/Burst.h
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_SERVER_H
-#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_SERVER_H
-
-#include "ExecutionBurstUtils.h"
+#ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_ADAPTER_BURST_H
+#define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_ADAPTER_BURST_H
 
 #include <android-base/thread_annotations.h>
 #include <android/hardware/neuralnetworks/1.0/types.h>
 #include <android/hardware/neuralnetworks/1.2/IBurstCallback.h>
+#include <android/hardware/neuralnetworks/1.2/IBurstContext.h>
 #include <android/hardware/neuralnetworks/1.2/IPreparedModel.h>
 #include <android/hardware/neuralnetworks/1.2/types.h>
 #include <fmq/MessageQueue.h>
@@ -29,7 +28,8 @@
 #include <nnapi/IBurst.h>
 #include <nnapi/Result.h>
 #include <nnapi/Types.h>
-#include <nnapi/hal/ProtectCallback.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
+#include <nnapi/hal/1.2/BurstUtils.h>
 
 #include <atomic>
 #include <chrono>
@@ -39,13 +39,13 @@
 #include <tuple>
 #include <vector>
 
-namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace android::hardware::neuralnetworks::adapter {
 
 /**
- * The ExecutionBurstServer class is responsible for waiting for and deserializing a request object
- * from a FMQ, performing the inference, and serializing the result back across another FMQ.
+ * The Burst class is responsible for waiting for and deserializing a request object from a FMQ,
+ * performing the inference, and serializing the result back across another FMQ.
  */
-class ExecutionBurstServer : public IBurstContext {
+class Burst : public V1_2::IBurstContext {
     struct PrivateConstructorTag {};
 
   public:
@@ -58,13 +58,13 @@
       public:
         // Precondition: burstExecutor != nullptr
         // Precondition: burstCallback != nullptr
-        MemoryCache(nn::SharedBurst burstExecutor, sp<IBurstCallback> burstCallback);
+        MemoryCache(nn::SharedBurst burstExecutor, sp<V1_2::IBurstCallback> burstCallback);
 
         /**
          * Get the cached memory objects corresponding to provided slot identifiers.
          *
-         * If the slot entry is not present in the cache, this class will use IBurstCallback to
-         * retrieve those entries that are not present in the cache, then cache them.
+         * If the slot entry is not present in the cache, this class will use V1_2::IBurstCallback
+         * to retrieve those entries that are not present in the cache, then cache them.
          *
          * @param slots Identifiers of memory objects to be retrieved.
          * @return A vector where each element is the memory object and a ref-counted cache "hold"
@@ -93,7 +93,7 @@
         std::map<int32_t, std::pair<nn::SharedMemory, nn::IBurst::OptionalCacheHold>> mCache
                 GUARDED_BY(mMutex);
         nn::SharedBurst kBurstExecutor;
-        const sp<IBurstCallback> kBurstCallback;
+        const sp<V1_2::IBurstCallback> kBurstCallback;
     };
 
     /**
@@ -111,45 +111,45 @@
      *     execution.
      * @param burstExecutor Object which maintains a local cache of the memory pools and executes
      *     using the cached memory pools.
-     * @param pollingTimeWindow How much time (in microseconds) the ExecutionBurstServer is allowed
-     *     to poll the FMQ before waiting on the blocking futex. Polling may result in lower
-     *     latencies at the potential cost of more power usage.
-     * @return IBurstContext Handle to the burst context.
+     * @param pollingTimeWindow How much time (in microseconds) the Burst is allowed to poll the FMQ
+     *     before waiting on the blocking futex. Polling may result in lower latencies at the
+     *     potential cost of more power usage.
+     * @return V1_2::IBurstContext Handle to the burst context.
      */
-    static nn::GeneralResult<sp<ExecutionBurstServer>> create(
-            const sp<IBurstCallback>& callback,
-            const MQDescriptorSync<FmqRequestDatum>& requestChannel,
-            const MQDescriptorSync<FmqResultDatum>& resultChannel, nn::SharedBurst burstExecutor,
+    static nn::GeneralResult<sp<Burst>> create(
+            const sp<V1_2::IBurstCallback>& callback,
+            const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+            const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
+            nn::SharedBurst burstExecutor,
             std::chrono::microseconds pollingTimeWindow = std::chrono::microseconds{0});
 
-    ExecutionBurstServer(PrivateConstructorTag tag, const sp<IBurstCallback>& callback,
-                         std::unique_ptr<RequestChannelReceiver> requestChannel,
-                         std::unique_ptr<ResultChannelSender> resultChannel,
-                         nn::SharedBurst burstExecutor);
-    ~ExecutionBurstServer();
+    Burst(PrivateConstructorTag tag, const sp<V1_2::IBurstCallback>& callback,
+          std::unique_ptr<V1_2::utils::RequestChannelReceiver> requestChannel,
+          std::unique_ptr<V1_2::utils::ResultChannelSender> resultChannel,
+          nn::SharedBurst burstExecutor);
+    ~Burst();
 
     // Used by the NN runtime to preemptively remove any stored memory. See
-    // IBurstContext::freeMemory for more information.
+    // V1_2::IBurstContext::freeMemory for more information.
     Return<void> freeMemory(int32_t slot) override;
 
   private:
-    // Work loop that will continue processing execution requests until the ExecutionBurstServer
-    // object is freed.
+    // Work loop that will continue processing execution requests until the Burst object is freed.
     void task();
 
-    nn::ExecutionResult<std::pair<hidl_vec<OutputShape>, Timing>> execute(
+    nn::ExecutionResult<std::pair<hidl_vec<V1_2::OutputShape>, V1_2::Timing>> execute(
             const V1_0::Request& requestWithoutPools, const std::vector<int32_t>& slotsOfPools,
-            MeasureTiming measure);
+            V1_2::MeasureTiming measure);
 
     std::thread mWorker;
     std::atomic<bool> mTeardown{false};
-    const sp<IBurstCallback> mCallback;
-    const std::unique_ptr<RequestChannelReceiver> mRequestChannelReceiver;
-    const std::unique_ptr<ResultChannelSender> mResultChannelSender;
+    const sp<V1_2::IBurstCallback> mCallback;
+    const std::unique_ptr<V1_2::utils::RequestChannelReceiver> mRequestChannelReceiver;
+    const std::unique_ptr<V1_2::utils::ResultChannelSender> mResultChannelSender;
     const nn::SharedBurst mBurstExecutor;
     MemoryCache mMemoryCache;
 };
 
-}  // namespace android::hardware::neuralnetworks::V1_2::utils
+}  // namespace android::hardware::neuralnetworks::adapter
 
-#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_1_2_UTILS_EXECUTION_BURST_SERVER_H
+#endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_ADAPTER_BURST_H
diff --git a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp b/neuralnetworks/utils/adapter/src/Burst.cpp
similarity index 65%
rename from neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
rename to neuralnetworks/utils/adapter/src/Burst.cpp
index 65ec7f5..8b2e1dd 100644
--- a/neuralnetworks/1.2/utils/src/ExecutionBurstServer.cpp
+++ b/neuralnetworks/utils/adapter/src/Burst.cpp
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "ExecutionBurstServer"
-
-#include "ExecutionBurstServer.h"
-#include "Conversions.h"
-#include "ExecutionBurstUtils.h"
+#include "Burst.h"
 
 #include <android-base/logging.h>
 #include <nnapi/IBurst.h>
@@ -27,8 +23,10 @@
 #include <nnapi/Types.h>
 #include <nnapi/Validation.h>
 #include <nnapi/hal/1.0/Conversions.h>
-#include <nnapi/hal/HandleError.h>
-#include <nnapi/hal/ProtectCallback.h>
+#include <nnapi/hal/1.0/HandleError.h>
+#include <nnapi/hal/1.0/ProtectCallback.h>
+#include <nnapi/hal/1.2/BurstUtils.h>
+#include <nnapi/hal/1.2/Conversions.h>
 #include <nnapi/hal/TransferValue.h>
 
 #include <algorithm>
@@ -42,15 +40,15 @@
 
 #include "Tracing.h"
 
-namespace android::hardware::neuralnetworks::V1_2::utils {
+namespace android::hardware::neuralnetworks::adapter {
 namespace {
 
-constexpr V1_2::Timing kNoTiming = {std::numeric_limits<uint64_t>::max(),
-                                    std::numeric_limits<uint64_t>::max()};
+constexpr V1_2::Timing kTiming = {std::numeric_limits<uint64_t>::max(),
+                                  std::numeric_limits<uint64_t>::max()};
 
 nn::GeneralResult<std::vector<nn::SharedMemory>> getMemoriesCallback(
         V1_0::ErrorStatus status, const hidl_vec<hidl_memory>& memories) {
-    HANDLE_HAL_STATUS(status) << "getting burst memories failed with " << toString(status);
+    HANDLE_STATUS_HIDL(status) << "getting burst memories failed with " << toString(status);
     std::vector<nn::SharedMemory> canonicalMemories;
     canonicalMemories.reserve(memories.size());
     for (const auto& memory : memories) {
@@ -61,15 +59,15 @@
 
 }  // anonymous namespace
 
-ExecutionBurstServer::MemoryCache::MemoryCache(nn::SharedBurst burstExecutor,
-                                               sp<IBurstCallback> burstCallback)
+Burst::MemoryCache::MemoryCache(nn::SharedBurst burstExecutor,
+                                sp<V1_2::IBurstCallback> burstCallback)
     : kBurstExecutor(std::move(burstExecutor)), kBurstCallback(std::move(burstCallback)) {
     CHECK(kBurstExecutor != nullptr);
     CHECK(kBurstCallback != nullptr);
 }
 
 nn::GeneralResult<std::vector<std::pair<nn::SharedMemory, nn::IBurst::OptionalCacheHold>>>
-ExecutionBurstServer::MemoryCache::getCacheEntries(const std::vector<int32_t>& slots) {
+Burst::MemoryCache::getCacheEntries(const std::vector<int32_t>& slots) {
     std::lock_guard guard(mMutex);
     NN_TRY(ensureCacheEntriesArePresentLocked(slots));
 
@@ -82,7 +80,7 @@
     return results;
 }
 
-nn::GeneralResult<void> ExecutionBurstServer::MemoryCache::ensureCacheEntriesArePresentLocked(
+nn::GeneralResult<void> Burst::MemoryCache::ensureCacheEntriesArePresentLocked(
         const std::vector<int32_t>& slots) {
     const auto slotIsKnown = [this](int32_t slot)
                                      REQUIRES(mMutex) { return mCache.count(slot) > 0; };
@@ -107,11 +105,10 @@
     auto returnedMemories = NN_TRY(cb.take());
 
     if (returnedMemories.size() != unknownSlots.size()) {
-        return NN_ERROR()
-               << "ExecutionBurstServer::MemoryCache::ensureCacheEntriesArePresentLocked: Error "
-                  "retrieving memories -- count mismatch between requested memories ("
-               << unknownSlots.size() << ") and returned memories (" << returnedMemories.size()
-               << ")";
+        return NN_ERROR() << "Burst::MemoryCache::ensureCacheEntriesArePresentLocked: Error "
+                             "retrieving memories -- count mismatch between requested memories ("
+                          << unknownSlots.size() << ") and returned memories ("
+                          << returnedMemories.size() << ")";
     }
 
     // add memories to unknown slots
@@ -123,56 +120,54 @@
 }
 
 nn::GeneralResult<std::pair<nn::SharedMemory, nn::IBurst::OptionalCacheHold>>
-ExecutionBurstServer::MemoryCache::getCacheEntryLocked(int32_t slot) {
+Burst::MemoryCache::getCacheEntryLocked(int32_t slot) {
     if (const auto iter = mCache.find(slot); iter != mCache.end()) {
         return iter->second;
     }
-    return NN_ERROR()
-           << "ExecutionBurstServer::MemoryCache::getCacheEntryLocked failed because slot " << slot
-           << " is not present in the cache";
+    return NN_ERROR() << "Burst::MemoryCache::getCacheEntryLocked failed because slot " << slot
+                      << " is not present in the cache";
 }
 
-void ExecutionBurstServer::MemoryCache::addCacheEntryLocked(int32_t slot, nn::SharedMemory memory) {
+void Burst::MemoryCache::addCacheEntryLocked(int32_t slot, nn::SharedMemory memory) {
     auto hold = kBurstExecutor->cacheMemory(memory);
     mCache.emplace(slot, std::make_pair(std::move(memory), std::move(hold)));
 }
 
-void ExecutionBurstServer::MemoryCache::removeCacheEntry(int32_t slot) {
+void Burst::MemoryCache::removeCacheEntry(int32_t slot) {
     std::lock_guard guard(mMutex);
     mCache.erase(slot);
 }
 
-// ExecutionBurstServer methods
+// Burst methods
 
-nn::GeneralResult<sp<ExecutionBurstServer>> ExecutionBurstServer::create(
-        const sp<IBurstCallback>& callback, const MQDescriptorSync<FmqRequestDatum>& requestChannel,
-        const MQDescriptorSync<FmqResultDatum>& resultChannel, nn::SharedBurst burstExecutor,
+nn::GeneralResult<sp<Burst>> Burst::create(
+        const sp<V1_2::IBurstCallback>& callback,
+        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel, nn::SharedBurst burstExecutor,
         std::chrono::microseconds pollingTimeWindow) {
     // check inputs
     if (callback == nullptr || burstExecutor == nullptr) {
-        return NN_ERROR() << "ExecutionBurstServer::create passed a nullptr";
+        return NN_ERROR() << "Burst::create passed a nullptr";
     }
 
     // create FMQ objects
     auto requestChannelReceiver =
-            NN_TRY(RequestChannelReceiver::create(requestChannel, pollingTimeWindow));
-    auto resultChannelSender = NN_TRY(ResultChannelSender::create(resultChannel));
+            NN_TRY(V1_2::utils::RequestChannelReceiver::create(requestChannel, pollingTimeWindow));
+    auto resultChannelSender = NN_TRY(V1_2::utils::ResultChannelSender::create(resultChannel));
 
     // check FMQ objects
     CHECK(requestChannelReceiver != nullptr);
     CHECK(resultChannelSender != nullptr);
 
     // make and return context
-    return sp<ExecutionBurstServer>::make(PrivateConstructorTag{}, callback,
-                                          std::move(requestChannelReceiver),
-                                          std::move(resultChannelSender), std::move(burstExecutor));
+    return sp<Burst>::make(PrivateConstructorTag{}, callback, std::move(requestChannelReceiver),
+                           std::move(resultChannelSender), std::move(burstExecutor));
 }
 
-ExecutionBurstServer::ExecutionBurstServer(PrivateConstructorTag /*tag*/,
-                                           const sp<IBurstCallback>& callback,
-                                           std::unique_ptr<RequestChannelReceiver> requestChannel,
-                                           std::unique_ptr<ResultChannelSender> resultChannel,
-                                           nn::SharedBurst burstExecutor)
+Burst::Burst(PrivateConstructorTag /*tag*/, const sp<V1_2::IBurstCallback>& callback,
+             std::unique_ptr<V1_2::utils::RequestChannelReceiver> requestChannel,
+             std::unique_ptr<V1_2::utils::ResultChannelSender> resultChannel,
+             nn::SharedBurst burstExecutor)
     : mCallback(callback),
       mRequestChannelReceiver(std::move(requestChannel)),
       mResultChannelSender(std::move(resultChannel)),
@@ -182,7 +177,7 @@
     mWorker = std::thread([this] { task(); });
 }
 
-ExecutionBurstServer::~ExecutionBurstServer() {
+Burst::~Burst() {
     // set teardown flag
     mTeardown = true;
     mRequestChannelReceiver->invalidate();
@@ -191,12 +186,12 @@
     mWorker.join();
 }
 
-Return<void> ExecutionBurstServer::freeMemory(int32_t slot) {
+Return<void> Burst::freeMemory(int32_t slot) {
     mMemoryCache.removeCacheEntry(slot);
     return Void();
 }
 
-void ExecutionBurstServer::task() {
+void Burst::task() {
     // loop until the burst object is being destroyed
     while (!mTeardown) {
         // receive request
@@ -208,12 +203,12 @@
         // if the burst is being torn down, skip the execution so the "task" function can end
         if (!arguments.has_value()) {
             if (!mTeardown) {
-                mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+                mResultChannelSender->send(V1_0::ErrorStatus::GENERAL_FAILURE, {}, kTiming);
             }
             continue;
         }
 
-        // unpack the arguments; types are Request, std::vector<int32_t>, and MeasureTiming,
+        // unpack the arguments; types are Request, std::vector<int32_t>, and V1_2::MeasureTiming,
         // respectively
         const auto [requestWithoutPools, slotsOfPools, measure] = std::move(arguments).value();
 
@@ -226,17 +221,17 @@
         } else {
             const auto& [message, code, outputShapes] = result.error();
             LOG(ERROR) << "IBurst::execute failed with " << code << ": " << message;
-            mResultChannelSender->send(convert(code).value(), convert(outputShapes).value(),
-                                       kNoTiming);
+            mResultChannelSender->send(V1_2::utils::convert(code).value(),
+                                       V1_2::utils::convert(outputShapes).value(), kTiming);
         }
     }
 }
 
-nn::ExecutionResult<std::pair<hidl_vec<OutputShape>, Timing>> ExecutionBurstServer::execute(
+nn::ExecutionResult<std::pair<hidl_vec<V1_2::OutputShape>, V1_2::Timing>> Burst::execute(
         const V1_0::Request& requestWithoutPools, const std::vector<int32_t>& slotsOfPools,
-        MeasureTiming measure) {
+        V1_2::MeasureTiming measure) {
     NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_EXECUTION,
-                 "ExecutionBurstServer getting memory, executing, and returning results");
+                 "Burst getting memory, executing, and returning results");
 
     // ensure executor with cache has required memory
     const auto cacheEntries = NN_TRY(mMemoryCache.getCacheEntries(slotsOfPools));
@@ -257,7 +252,8 @@
     const auto [outputShapes, timing] =
             NN_TRY(mBurstExecutor->execute(canonicalRequest, canonicalMeasure, {}, {}));
 
-    return std::make_pair(NN_TRY(convert(outputShapes)), NN_TRY(convert(timing)));
+    return std::make_pair(NN_TRY(V1_2::utils::convert(outputShapes)),
+                          NN_TRY(V1_2::utils::convert(timing)));
 }
 
-}  // namespace android::hardware::neuralnetworks::V1_2::utils
+}  // namespace android::hardware::neuralnetworks::adapter
diff --git a/neuralnetworks/utils/adapter/src/PreparedModel.cpp b/neuralnetworks/utils/adapter/src/PreparedModel.cpp
index 7397def..a14e782 100644
--- a/neuralnetworks/utils/adapter/src/PreparedModel.cpp
+++ b/neuralnetworks/utils/adapter/src/PreparedModel.cpp
@@ -16,7 +16,8 @@
 
 #include "PreparedModel.h"
 
-#include <ExecutionBurstServer.h>
+#include "Burst.h"
+
 #include <android-base/logging.h>
 #include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h>
 #include <android/hardware/neuralnetworks/1.0/types.h>
@@ -36,7 +37,6 @@
 #include <nnapi/hal/1.2/Utils.h>
 #include <nnapi/hal/1.3/Conversions.h>
 #include <nnapi/hal/1.3/Utils.h>
-#include <nnapi/hal/HandleError.h>
 #include <sys/types.h>
 
 #include <memory>
@@ -273,6 +273,15 @@
     return syncFences;
 }
 
+nn::GeneralResult<sp<V1_2::IBurstContext>> configureExecutionBurst(
+        const nn::SharedPreparedModel& preparedModel, const sp<V1_2::IBurstCallback>& callback,
+        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
+        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel) {
+    auto burstExecutor = NN_TRY(preparedModel->configureExecutionBurst());
+    return Burst::create(callback, requestChannel, resultChannel, std::move(burstExecutor),
+                         V1_2::utils::getBurstServerPollingTimeWindow());
+}
+
 nn::GeneralResult<std::pair<hidl_handle, sp<V1_3::IFencedExecutionCallback>>> executeFenced(
         const nn::SharedPreparedModel& preparedModel, const V1_3::Request& request,
         const hidl_vec<hidl_handle>& waitFor, V1_2::MeasureTiming measure,
@@ -389,14 +398,17 @@
         const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
         const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
         configureExecutionBurst_cb cb) {
-    const sp<V1_2::IBurstContext> burst = nn::ExecutionBurstServer::create(
-            callback, requestChannel, resultChannel, this, std::chrono::microseconds{0});
-
-    if (burst == nullptr) {
-        cb(V1_0::ErrorStatus::GENERAL_FAILURE, {});
-    } else {
-        cb(V1_0::ErrorStatus::NONE, burst);
+    auto result = adapter::configureExecutionBurst(kPreparedModel, callback, requestChannel,
+                                                   resultChannel);
+    if (!result.has_value()) {
+        auto [message, code] = std::move(result).error();
+        LOG(ERROR) << "adapter::PreparedModel::configureExecutionBurst failed with " << code << ": "
+                   << message;
+        cb(V1_2::utils::convert(code).value(), nullptr);
+        return Void();
     }
+    auto burstContext = std::move(result).value();
+    cb(V1_0::ErrorStatus::NONE, std::move(burstContext));
     return Void();
 }
 
diff --git a/neuralnetworks/utils/common/Android.bp b/neuralnetworks/utils/common/Android.bp
index f88e407..39927a3 100644
--- a/neuralnetworks/utils/common/Android.bp
+++ b/neuralnetworks/utils/common/Android.bp
@@ -30,20 +30,7 @@
     local_include_dirs: ["include/nnapi/hal"],
     export_include_dirs: ["include"],
     cflags: ["-Wthread-safety"],
-    static_libs: [
-        "libarect",
-        "neuralnetworks_types",
-    ],
-    shared_libs: [
-        "android.hardware.neuralnetworks-V2-ndk",
-        "libhidlbase",
-        "libbinder_ndk",
-    ],
-    target: {
-        android: {
-            shared_libs: ["libnativewindow"],
-        },
-    },
+    static_libs: ["neuralnetworks_types"],
 }
 
 cc_test {
@@ -51,7 +38,6 @@
     host_supported: true,
     srcs: ["test/*.cpp"],
     static_libs: [
-        "android.hardware.neuralnetworks@1.0",
         "libgmock",
         "libneuralnetworks_common",
         "neuralnetworks_types",
diff --git a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
index 702ee92..ae0d092 100644
--- a/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
+++ b/neuralnetworks/utils/common/include/nnapi/hal/CommonUtils.h
@@ -17,8 +17,6 @@
 #ifndef ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H
 #define ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H
 
-#include <cutils/native_handle.h>
-#include <hidl/HidlSupport.h>
 #include <nnapi/Result.h>
 #include <nnapi/SharedMemory.h>
 #include <nnapi/Types.h>
@@ -125,18 +123,6 @@
         const nn::Request* request, uint32_t alignment, uint32_t padding,
         std::optional<nn::Request>* maybeRequestInSharedOut, RequestRelocation* relocationOut);
 
-nn::GeneralResult<std::vector<uint32_t>> countNumberOfConsumers(
-        size_t numberOfOperands, const std::vector<nn::Operation>& operations);
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory);
-nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory);
-
-nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle);
-nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle);
-
-nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
-        const std::vector<nn::SyncFence>& fences);
-
 }  // namespace android::hardware::neuralnetworks::utils
 
 #endif  // ANDROID_HARDWARE_INTERFACES_NEURALNETWORKS_UTILS_COMMON_COMMON_UTILS_H
diff --git a/neuralnetworks/utils/common/src/CommonUtils.cpp b/neuralnetworks/utils/common/src/CommonUtils.cpp
index 235ba29..b249881 100644
--- a/neuralnetworks/utils/common/src/CommonUtils.cpp
+++ b/neuralnetworks/utils/common/src/CommonUtils.cpp
@@ -16,11 +16,7 @@
 
 #include "CommonUtils.h"
 
-#include "HandleError.h"
-
 #include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <hidl/HidlSupport.h>
 #include <nnapi/Result.h>
 #include <nnapi/SharedMemory.h>
 #include <nnapi/TypeUtils.h>
@@ -34,11 +30,6 @@
 #include <variant>
 #include <vector>
 
-#ifdef __ANDROID__
-#include <android/hardware_buffer.h>
-#include <vndk/hardware_buffer.h>
-#endif  // __ANDROID__
-
 namespace android::hardware::neuralnetworks::utils {
 namespace {
 
@@ -92,97 +83,6 @@
                   });
 }
 
-nn::GeneralResult<hidl_handle> createNativeHandleFrom(std::vector<base::unique_fd> fds,
-                                                      const std::vector<int32_t>& ints) {
-    constexpr size_t kIntMax = std::numeric_limits<int>::max();
-    CHECK_LE(fds.size(), kIntMax);
-    CHECK_LE(ints.size(), kIntMax);
-    native_handle_t* nativeHandle =
-            native_handle_create(static_cast<int>(fds.size()), static_cast<int>(ints.size()));
-    if (nativeHandle == nullptr) {
-        return NN_ERROR() << "Failed to create native_handle";
-    }
-
-    for (size_t i = 0; i < fds.size(); ++i) {
-        nativeHandle->data[i] = fds[i].release();
-    }
-    std::copy(ints.begin(), ints.end(), nativeHandle->data + nativeHandle->numFds);
-
-    hidl_handle handle;
-    handle.setTo(nativeHandle, /*shouldOwn=*/true);
-    return handle;
-}
-
-nn::GeneralResult<hidl_handle> createNativeHandleFrom(base::unique_fd fd,
-                                                      const std::vector<int32_t>& ints) {
-    std::vector<base::unique_fd> fds;
-    fds.push_back(std::move(fd));
-    return createNativeHandleFrom(std::move(fds), ints);
-}
-
-nn::GeneralResult<hidl_handle> createNativeHandleFrom(const nn::Memory::Unknown::Handle& handle) {
-    std::vector<base::unique_fd> fds = NN_TRY(nn::dupFds(handle.fds.begin(), handle.fds.end()));
-    return createNativeHandleFrom(std::move(fds), handle.ints);
-}
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Ashmem& memory) {
-    auto fd = NN_TRY(nn::dupFd(memory.fd));
-    auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), {}));
-    return hidl_memory("ashmem", std::move(handle), memory.size);
-}
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Fd& memory) {
-    auto fd = NN_TRY(nn::dupFd(memory.fd));
-
-    const auto [lowOffsetBits, highOffsetBits] = nn::getIntsFromOffset(memory.offset);
-    const std::vector<int> ints = {memory.prot, lowOffsetBits, highOffsetBits};
-
-    auto handle = NN_TRY(createNativeHandleFrom(std::move(fd), ints));
-    return hidl_memory("mmap_fd", std::move(handle), memory.size);
-}
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::HardwareBuffer& memory) {
-#ifdef __ANDROID__
-    const auto* ahwb = memory.handle.get();
-    AHardwareBuffer_Desc bufferDesc;
-    AHardwareBuffer_describe(ahwb, &bufferDesc);
-
-    const bool isBlob = bufferDesc.format == AHARDWAREBUFFER_FORMAT_BLOB;
-    const size_t size = isBlob ? bufferDesc.width : 0;
-    const char* const name = isBlob ? "hardware_buffer_blob" : "hardware_buffer";
-
-    const native_handle_t* nativeHandle = AHardwareBuffer_getNativeHandle(ahwb);
-    const hidl_handle hidlHandle(nativeHandle);
-    hidl_handle copiedHandle(hidlHandle);
-
-    return hidl_memory(name, std::move(copiedHandle), size);
-#else   // __ANDROID__
-    LOG(FATAL) << "nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const "
-                  "nn::Memory::HardwareBuffer& memory): Not Available on Host Build";
-    (void)memory;
-    return (NN_ERROR() << "createHidlMemoryFrom failed").operator nn::GeneralResult<hidl_memory>();
-#endif  // __ANDROID__
-}
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFrom(const nn::Memory::Unknown& memory) {
-    return hidl_memory(memory.name, NN_TRY(createNativeHandleFrom(memory.handle)), memory.size);
-}
-
-nn::GeneralResult<nn::Memory::Unknown::Handle> unknownHandleFromNativeHandle(
-        const native_handle_t* handle) {
-    if (handle == nullptr) {
-        return NN_ERROR() << "unknownHandleFromNativeHandle failed because handle is nullptr";
-    }
-
-    std::vector<base::unique_fd> fds =
-            NN_TRY(nn::dupFds(handle->data + 0, handle->data + handle->numFds));
-
-    std::vector<int> ints(handle->data + handle->numFds,
-                          handle->data + handle->numFds + handle->numInts);
-
-    return nn::Memory::Unknown::Handle{.fds = std::move(fds), .ints = std::move(ints)};
-}
-
 }  // anonymous namespace
 
 nn::Capabilities::OperandPerformanceTable makeQuantized8PerformanceConsistentWithP(
@@ -331,147 +231,4 @@
     return **maybeRequestInSharedOut;
 }
 
-nn::GeneralResult<std::vector<uint32_t>> countNumberOfConsumers(
-        size_t numberOfOperands, const std::vector<nn::Operation>& operations) {
-    return nn::countNumberOfConsumers(numberOfOperands, operations);
-}
-
-nn::GeneralResult<hidl_memory> createHidlMemoryFromSharedMemory(const nn::SharedMemory& memory) {
-    if (memory == nullptr) {
-        return NN_ERROR() << "Memory must be non-empty";
-    }
-    return std::visit([](const auto& x) { return createHidlMemoryFrom(x); }, memory->handle);
-}
-
-#ifdef __ANDROID__
-static uint32_t roundUpToMultiple(uint32_t value, uint32_t multiple) {
-    return (value + multiple - 1) / multiple * multiple;
-}
-#endif  // __ANDROID__
-
-nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const hidl_memory& memory) {
-    CHECK_LE(memory.size(), std::numeric_limits<size_t>::max());
-    if (!memory.valid()) {
-        return NN_ERROR() << "Unable to convert invalid hidl_memory";
-    }
-
-    if (memory.name() == "ashmem") {
-        if (memory.handle()->numFds != 1) {
-            return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
-                              << memory.handle()->numFds << " numFds, but expected 1";
-        }
-        if (memory.handle()->numInts != 0) {
-            return NN_ERROR() << "Unable to convert invalid ashmem memory object with "
-                              << memory.handle()->numInts << " numInts, but expected 0";
-        }
-        auto handle = nn::Memory::Ashmem{
-                .fd = NN_TRY(nn::dupFd(memory.handle()->data[0])),
-                .size = static_cast<size_t>(memory.size()),
-        };
-        return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
-    }
-
-    if (memory.name() == "mmap_fd") {
-        if (memory.handle()->numFds != 1) {
-            return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
-                              << memory.handle()->numFds << " numFds, but expected 1";
-        }
-        if (memory.handle()->numInts != 3) {
-            return NN_ERROR() << "Unable to convert invalid mmap_fd memory object with "
-                              << memory.handle()->numInts << " numInts, but expected 3";
-        }
-
-        const int fd = memory.handle()->data[0];
-        const int prot = memory.handle()->data[1];
-        const int lower = memory.handle()->data[2];
-        const int higher = memory.handle()->data[3];
-        const size_t offset = nn::getOffsetFromInts(lower, higher);
-
-        return nn::createSharedMemoryFromFd(static_cast<size_t>(memory.size()), prot, fd, offset);
-    }
-
-    if (memory.name() != "hardware_buffer_blob") {
-        auto handle = nn::Memory::Unknown{
-                .handle = NN_TRY(unknownHandleFromNativeHandle(memory.handle())),
-                .size = static_cast<size_t>(memory.size()),
-                .name = memory.name(),
-        };
-        return std::make_shared<const nn::Memory>(nn::Memory{.handle = std::move(handle)});
-    }
-
-#ifdef __ANDROID__
-    const auto size = memory.size();
-    const auto format = AHARDWAREBUFFER_FORMAT_BLOB;
-    const auto usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
-    const uint32_t width = size;
-    const uint32_t height = 1;  // height is always 1 for BLOB mode AHardwareBuffer.
-    const uint32_t layers = 1;  // layers is always 1 for BLOB mode AHardwareBuffer.
-
-    // AHardwareBuffer_createFromHandle() might fail because an allocator
-    // expects a specific stride value. In that case, we try to guess it by
-    // aligning the width to small powers of 2.
-    // TODO(b/174120849): Avoid stride assumptions.
-    AHardwareBuffer* hardwareBuffer = nullptr;
-    status_t status = UNKNOWN_ERROR;
-    for (uint32_t alignment : {1, 4, 32, 64, 128, 2, 8, 16}) {
-        const uint32_t stride = roundUpToMultiple(width, alignment);
-        AHardwareBuffer_Desc desc{
-                .width = width,
-                .height = height,
-                .layers = layers,
-                .format = format,
-                .usage = usage,
-                .stride = stride,
-        };
-        status = AHardwareBuffer_createFromHandle(&desc, memory.handle(),
-                                                  AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE,
-                                                  &hardwareBuffer);
-        if (status == NO_ERROR) {
-            break;
-        }
-    }
-    if (status != NO_ERROR) {
-        return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
-               << "Can't create AHardwareBuffer from handle. Error: " << status;
-    }
-
-    return nn::createSharedMemoryFromAHWB(hardwareBuffer, /*takeOwnership=*/true);
-#else   // __ANDROID__
-    LOG(FATAL) << "nn::GeneralResult<nn::SharedMemory> createSharedMemoryFromHidlMemory(const "
-                  "hidl_memory& memory): Not Available on Host Build";
-    return (NN_ERROR() << "createSharedMemoryFromHidlMemory failed")
-            .
-            operator nn::GeneralResult<nn::SharedMemory>();
-#endif  // __ANDROID__
-}
-
-nn::GeneralResult<hidl_handle> hidlHandleFromSharedHandle(const nn::Handle& handle) {
-    base::unique_fd fd = NN_TRY(nn::dupFd(handle.get()));
-    return createNativeHandleFrom(std::move(fd), {});
-}
-
-nn::GeneralResult<nn::Handle> sharedHandleFromNativeHandle(const native_handle_t* handle) {
-    if (handle == nullptr) {
-        return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle is nullptr";
-    }
-    if (handle->numFds != 1 || handle->numInts != 0) {
-        return NN_ERROR() << "sharedHandleFromNativeHandle failed because handle does not only "
-                             "hold a single fd";
-    }
-    return nn::dupFd(handle->data[0]);
-}
-
-nn::GeneralResult<hidl_vec<hidl_handle>> convertSyncFences(
-        const std::vector<nn::SyncFence>& syncFences) {
-    hidl_vec<hidl_handle> handles(syncFences.size());
-    for (size_t i = 0; i < syncFences.size(); ++i) {
-        const auto& handle = syncFences[i].getSharedHandle();
-        if (handle == nullptr) {
-            return NN_ERROR() << "convertSyncFences failed because sync fence is empty";
-        }
-        handles[i] = NN_TRY(hidlHandleFromSharedHandle(*handle));
-    }
-    return handles;
-}
-
 }  // namespace android::hardware::neuralnetworks::utils
diff --git a/neuralnetworks/utils/service/Android.bp b/neuralnetworks/utils/service/Android.bp
index fbb8679..c3272ae 100644
--- a/neuralnetworks/utils/service/Android.bp
+++ b/neuralnetworks/utils/service/Android.bp
@@ -25,7 +25,10 @@
 
 cc_library_static {
     name: "neuralnetworks_utils_hal_service",
-    defaults: ["neuralnetworks_utils_defaults"],
+    defaults: [
+        "neuralnetworks_use_latest_utils_hal_aidl",
+        "neuralnetworks_utils_defaults",
+    ],
     srcs: ["src/*"],
     local_include_dirs: ["include/nnapi/hal"],
     export_include_dirs: ["include"],
@@ -35,15 +38,12 @@
         "neuralnetworks_utils_hal_1_1",
         "neuralnetworks_utils_hal_1_2",
         "neuralnetworks_utils_hal_1_3",
-        "neuralnetworks_utils_hal_aidl",
         "neuralnetworks_utils_hal_common",
     ],
     shared_libs: [
-        "android.hardware.neuralnetworks-V2-ndk",
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
         "android.hardware.neuralnetworks@1.2",
         "android.hardware.neuralnetworks@1.3",
-        "libbinder_ndk",
     ],
 }
diff --git a/power/aidl/default/Android.bp b/power/aidl/default/Android.bp
index e10d329..9acb9e0 100644
--- a/power/aidl/default/Android.bp
+++ b/power/aidl/default/Android.bp
@@ -24,8 +24,8 @@
 cc_binary {
     name: "android.hardware.power-service.example",
     relative_install_path: "hw",
-    init_rc: ["power-default.rc"],
-    vintf_fragments: ["power-default.xml"],
+    init_rc: [":android.hardware.power.rc"],
+    vintf_fragments: [":android.hardware.power.xml"],
     vendor: true,
     shared_libs: [
         "libbase",
@@ -37,3 +37,13 @@
         "Power.cpp",
     ],
 }
+
+filegroup {
+    name: "android.hardware.power.xml",
+    srcs: ["power-default.xml"],
+}
+
+filegroup {
+    name: "android.hardware.power.rc",
+    srcs: ["power-default.rc"],
+}
diff --git a/power/aidl/default/apex/Android.bp b/power/aidl/default/apex/Android.bp
new file mode 100644
index 0000000..eb04087
--- /dev/null
+++ b/power/aidl/default/apex/Android.bp
@@ -0,0 +1,72 @@
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+    name: "com.android.hardware.power.key",
+    public_key: "com.android.hardware.power.avbpubkey",
+    private_key: "com.android.hardware.power.pem",
+}
+
+android_app_certificate {
+    name: "com.android.hardware.power.certificate",
+    certificate: "com.android.hardware.power",
+}
+
+genrule {
+    name: "com.android.hardware.power.rc-srcs",
+    srcs: [
+        ":android.hardware.power.rc",
+        ":android.hardware.power.stats.rc",
+    ],
+    out: ["com.android.hardware.power.rc"],
+    cmd: "sed -E 's/\\/vendor/\\/apex\\/com.android.hardware.power/' $(in) > $(out)",
+}
+
+prebuilt_etc {
+    name: "com.android.hardware.power.rc",
+    src: ":com.android.hardware.power.rc-srcs",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.power",
+    manifest: "apex_manifest.json",
+    key: "com.android.hardware.power.key",
+    certificate: ":com.android.hardware.power.certificate",
+    file_contexts: "file_contexts",
+    use_vndk_as_stable: true,
+    updatable: false,
+    // Install the apex in /vendor/apex
+    soc_specific: true,
+    // Bundle the Power and PowerStats HALs into this one APEX.
+    binaries: [
+        "android.hardware.power-service.example",
+        "android.hardware.power.stats-service.example",
+    ],
+    prebuilts: [
+        "com.android.hardware.power.rc",
+    ],
+    vintf_fragments: [
+        ":android.hardware.power.xml",
+        ":android.hardware.power.stats.xml",
+    ],
+    overrides: [
+        // Shared lib installed by default but unused by the AIDL implementation.
+        "power.default",
+    ],
+}
diff --git a/power/aidl/default/apex/apex_manifest.json b/power/aidl/default/apex/apex_manifest.json
new file mode 100644
index 0000000..faa937d
--- /dev/null
+++ b/power/aidl/default/apex/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.hardware.power",
+  "version": 1
+}
diff --git a/power/aidl/default/apex/com.android.hardware.power.avbpubkey b/power/aidl/default/apex/com.android.hardware.power.avbpubkey
new file mode 100644
index 0000000..3b6411d
--- /dev/null
+++ b/power/aidl/default/apex/com.android.hardware.power.avbpubkey
Binary files differ
diff --git a/power/aidl/default/apex/com.android.hardware.power.pem b/power/aidl/default/apex/com.android.hardware.power.pem
new file mode 100644
index 0000000..d18ae98
--- /dev/null
+++ b/power/aidl/default/apex/com.android.hardware.power.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAsUdFjtLq05tWKdQd4aj8V7tmV4KXw41pKGT5Q1CPzrdHF3yJ
+B/8VWdMpjZ+eQO1q8SijPgfvWExeWQVMfxKmwTmj26xWXhIOgo5G02Zva7zOptig
+KGnl/RdFlOiIGC36XeWDhzdIOhlGv+er9Sykf6Ot84OvktTBUTZNJrXQsyYTBRUX
+6B+wloPdBVxVf1HgzjeHUyCy5dTz0xZSWWELoW24tHIvV5FtJVKSY8ZDfuXWLLfT
+he7E93TepjT027U/J/iW4ITJzw4Qq87ick1D/jZDUbTrkqUMFEgPdgCouZ9zt5xG
+pcHAZ/Fcz9DZfEdX9Xy0R5/XbfrJdvDPguJlwK1pZnr/Pe13xxmE+TEohMmaQDqX
+jQNX3UlcfOYUAjnFMGucHDM10KjTMbP8ytCys88aNLiv7FOgVGrQ/djZN8rkMyVP
+ccoksUBMQmjYaQQZ2yZuJMiLD3P6aYkgU5tMEMoMTrBzfcx05GfElal+ZqOFKAzj
+eUxoZTR27wJb684FRbeE45D+y4jpFfrTEXry+aI7GrfDsVDnUqmyObCUttRtaT04
+6kuUmC44wFEg1EBfcWZc1szI192GBjMuZjFcYvJ4vMdCuennqvLNPBDY1PtqzCOf
+D8vpOE3T9rjS23xxFmmSmorwKKQOGChKqO/SaY0axkXgt+FbSsvTBQtZTSsCAwEA
+AQKCAgEArEu3t+MYJcdwS8EDtcF2V5IkGmMrOvziOkdA14Kh8efBKXt49xOc3atU
+VHfQ6AuXh4DWf0BZB7lZbS2wNkSbW2q47ZSmcFEeVxcOkQGawtxDAHfD2ONrirqm
+ft4s/0sYbU/WsIEzKnxMfdEdGHFmA0PCmczfxFYQ+OxMuZW1m5ginirtDEZYa0EH
+e+FMmyypz+K6HDnIhYWd4Aduy718/0zTWlUr2/DUYpTJAD2+dcPNj7Kt2xq/xj2I
+84K+hBa4phF+GgIU3a8u1ryA61RbA+QbM3siBWlxvvh2RlrHoXjuj4JMS2dup9c2
+PCggaCAyxb2IvaAFUbePPJE5LVz6RFT4HnLEydd5Yt+CEAm+iZKfCzyUgFRtb5y0
+HHTME1eVAt/rf/yIXUYA7q8nQ/PtSzIol5KLX4FUjX1MVKNmIWMa+836kxbuYDFB
+K1M1IKc1k0t9Q9F3TRCMhP/6qH/vJfubCGQhSRUMq7JyjivK9GjYST8R07Dpgu9Z
+4i9TRI8d+UVERsg8niCXONVkmNa3U49u2duUvqV3KmKgQ/Hgyy3keDjz6x56ie5w
+e0EusHAsot60W1BvHrdwlmGZjW3JmZEyazUPh9nBUAaQve1rIOpn80kGXx4EAE2o
+HcrcInJx/zVBk1Wk3UQDwmhUNpa64q9+nd9VMaR9SQNK3ah4NDECggEBAOeput2F
+CgRrvzka69i7FbgY4VmpNMIICPIB6gxvwpir/g4/GgYknuBB6ep1ksf/IZfsMp5A
+JTH1KdXqqQm8nV9v+ETYQAO+VnmWKSBKHsNJqONxsKkQ+xIJcusmKBTYLfL88XQg
+YWH3VMXgqPP8DnJYCeVRIKj1WqfEFFHiaLJJB8FgKhtZBwBnibkVG1K0XCkTdUfY
+mME2GRKW/C7DMvuFOpcFVj7Obwn68R2k3zsOhWA5NQGZF5mqhg5KYLVDg3IbMJQQ
+D+DymQxnc2s2ar0q24isy1Y/FOXrA057j1vAN951+pk6F/PCJM/mtAiRjhP0Aru1
+P6bbR11p+wnpU7MCggEBAMPm8Jmwu3F0xsyFC+1sWPAzPiwaMa7/30wANNKKqHVO
+7lUv1WYFbFMyAOzYPp3Y5HxdxNa43reULGk0R20kSu6W6FkApSvAws0rLKRlS5UI
+oZqhLGHUH2M7q07m2RgQY2TJkU2Zq6AH1kjcbSr127ISXKanKpqonwSHy38BTcGt
+Dl2fVioPzK/vwmiNo2njhh95TV4kqlbUfl7xtDt56tbg8oFBwOsK7UGajXYOxTGB
+o1DtO5E+oiOmlclXuo3m4qpSSMv+wM91aRFhHZVIx0vmO8y5lrfU2kM/5DDhJBxV
+FM4TaA+c5tFOTuCLejHc7nM99wVx7O4QZ0wBwETUxKkCggEAH0tBT+1J1iEL+tXV
+KDjVjUHnJyqBUvis5Kw3hqiOO/t33UrO5CeMQrUEuURaqKOhURl6GQCHRcFdfmUt
+ooAVLjA89GfV9et/WPtc4NzCXRUVOGxCNgRyNhSKrpM/9NjjFCDxKQO6w/YaQITB
+rfvNo8qaw5x68ff64BDPweP4yqSs5IVuCrWzCW3zH8pnH3v3uyDCxgrPT8JUDrvQ
+oyyBNZLgwEfbR66xN0Lr0VpVQXALulzf+TBKDNsJMuL/P104Y3Ci1k15J6T94bwT
+zlbSgm1IrKTS7vqkgw6FKtPsILPNmEKNsKc1VxtRx7fdeA7Zh3595Adu6sZSVJ8d
+Z1BamwKCAQAnbu0vgqu4gtEhigaEnDKq5yW0qvElUMwZ+FCpsM+IDYNcEmzaRG0x
+sfcNtdmk3GvhvN5KepwaR/FInAVkqtGKhUXv5Hla/Uo5El/CF8HHFh2xio/sgU5w
+IyqwjzdT6LiZKRnejPhHFkzEDdrLswGuLpQH185zo02fE9aakiCcw8EIh3JItTV2
+lMSFVz11qx7sZvZz5N2E7PEjG3Q0JK5o4o7uBdZXebOYaQvgn8iB1p6RQ6+h5QGu
+O3IbPVWICtnFfxq4NWeKWw/zN6FE04mKdaXD5/e2uVnV/55nWGp0aYvuj2l6+xJb
+P3ARMwI910MIX4jBx9TxdsvUOOYC9PFBAoIBAQDWswLnaNth4pgutngVWbMenSpv
+eK1RA1ldw2NoTZrGlqPB+LvjEMSH/7ioby8YtOyJRIWs3si8HpVF12qneu8qi7b7
+QlUtqyJOTnGalvhrlq5zPhdW+kk2DXvtTylUnz3vSxxi2I7cLhQRryLC/1kAwy67
+wEr0+u59bOvaqe8L1zgtYJpLQZeskUMzdSMIRVDdFShEFrMJU7adUvGpA7OZ6Ogf
+ux2jWr2vv/eKq6fU6kDPi/66MQjPbZPf2Uq6+XedkNkAeELpN4o3hw0/l/rfiK/r
+YUMJBwtjQw/hehtvC4GlgsH1tMZWzCZULo0tcW4qbzyi9PBrWFPteb33OjBc
+-----END RSA PRIVATE KEY-----
diff --git a/power/aidl/default/apex/com.android.hardware.power.pk8 b/power/aidl/default/apex/com.android.hardware.power.pk8
new file mode 100644
index 0000000..e45435d
--- /dev/null
+++ b/power/aidl/default/apex/com.android.hardware.power.pk8
Binary files differ
diff --git a/power/aidl/default/apex/com.android.hardware.power.x509.pem b/power/aidl/default/apex/com.android.hardware.power.x509.pem
new file mode 100644
index 0000000..9f0c5f0
--- /dev/null
+++ b/power/aidl/default/apex/com.android.hardware.power.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8ECFDFsXbm5CdS/UtQZgTiF8Umr8LrLMA0GCSqGSIb3DQEBCwUAMIGn
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEjMCEGA1UEAwwaY29t
+LmFuZHJvaWQuaGFyZHdhcmUucG93ZXIwIBcNMjExMDIwMTcwNTA0WhgPNDc1OTA5
+MTYxNzA1MDRaMIGnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW
+MBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UE
+CwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEj
+MCEGA1UEAwwaY29tLmFuZHJvaWQuaGFyZHdhcmUucG93ZXIwggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQC/37fhOkOfgM2e+M7bMJ1baLFif8dKGwRa6mkJ
+9HWmuOgRcTKllzuEHtrJ0jzur3cDy6/0oZSfA/E1ck3DdRHMQadW26JSNSg6fCU9
+h1kDzkakZgyr3DsJnKGoSHCJ2V2kVbCnd6GuOaOU1ZZISw1I+BWJDc3t1mZPs80D
+ar7/hoIZnsWRoE/eWgJDcWWscRsquSi+q6hyqlCbRvwRznPaDGwmb4geHNugCXkz
+EtCswfc5jiT8DjMDkgVsGO/WcYj2GWT0K0H+Zf1CmEO9fAoXTLfVBjdumtGILgii
+d/TJe2tOBSWyZz6sVzfac2PvUH5Lm8TNUXuLV5IEdcpySge0vqYQwAyd2EgsTH1e
+mRNSk9NerpmfCFEySRRP3BWMGRhbST1d8M3v9Bq0QFhrxoAF12r6GXBUpp9XcOL5
+pBTcAkA9XI++mfz4pDzyGRGOy4WX+8XtsaVZ/14JklupSLr0Tt7oaNocUhoXB03g
+4B0jUTX0hNnVzCxzJypw6YJ60Zc8z+z8pEF34FWarHec1QbkFuyWxbaTPQ4d2NLH
+8zDxQpMILErWdAgKsRL0d8RFG5fBcleEoBM2kKHMAgnP+1qyDqBgt8zloWbmmblw
+JXMuoePFOgeVcgPrZ3EGJSx+s4+dQGQc6r/GwKLKSWpUvHxTIGug76IX9xmptB+I
+F3xb2QIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQANISf3Vi2eueOlzzfnEGGa+CXz
+nvlgUXKv8Gv0/Pbg5uC1BaHTAUgRu5rvrfP9p3Mdj86I/HbE/F4Vkuzqb8/JTWGA
+mG636zAsJRJr0fnkbPma9wVEPSK8MF1QqM6PmKXboixX82TqV1R1sRYG+9hh9W3u
+isDzYDb2ODE0X9M8/3hLS28zdCdtl4zCRK6KB86aGxvkVEj4qDA5l+AbVYevS/SU
+hz1+K/aM0Fi6MZovo5kd/Mof5l05e1TEgCoL1FtFX79r+PYGHJ8/LjtEMkFgwqvG
+CLx2sOV09EHZU27EbVvSs1JYMMXgeAvKaHsVZ51QlSzW4esg/E6z4pw654p8qyK/
+WLXIZ7BMILl1sHYmGqXitnu19puvNks2/+hyqVr0seM5GyQDuwBE8nx6xZzTRxdj
+4TZyN9LuMc9/cKJFvOPqD152bkA2frCLEzYCQreDWwxsWcUHzYrQT+v2SqzP6Ue2
+Xn06HDLx9wBL7Dz6no05SlNS0u1KdvKas6FKZHO+QaKKsBlDmXbMrBTcuUI6OXv2
+6NpVbeyDd0+A23hDiNSgI6zTY6jMidesNExB7rW/bCE4ltPyxFAB+sffyXounODc
+groB5CaS2bv+H1IXJzMMe4LkgQPl1C7G+I3KvJmnrYwmIhLIDuxP82arClIDzccS
+ExRR7ugEg91XCc87Zg==
+-----END CERTIFICATE-----
diff --git a/power/aidl/default/apex/file_contexts b/power/aidl/default/apex/file_contexts
new file mode 100644
index 0000000..3433851
--- /dev/null
+++ b/power/aidl/default/apex/file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                      u:object_r:vendor_file:s0
+/bin/hw/android\.hardware\.power-service\.example           u:object_r:hal_power_default_exec:s0
+/bin/hw/android\.hardware\.power\.stats-service\.example    u:object_r:hal_power_stats_default_exec:s0
diff --git a/power/stats/aidl/default/Android.bp b/power/stats/aidl/default/Android.bp
index 7c0caf3..66be5f9 100644
--- a/power/stats/aidl/default/Android.bp
+++ b/power/stats/aidl/default/Android.bp
@@ -24,8 +24,8 @@
 cc_binary {
     name: "android.hardware.power.stats-service.example",
     relative_install_path: "hw",
-    init_rc: ["power.stats-default.rc"],
-    vintf_fragments: ["power.stats-default.xml"],
+    init_rc: [":android.hardware.power.stats.rc"],
+    vintf_fragments: [":android.hardware.power.stats.xml"],
     vendor: true,
     shared_libs: [
         "libbase",
@@ -37,3 +37,13 @@
         "PowerStats.cpp",
     ],
 }
+
+filegroup {
+    name: "android.hardware.power.stats.xml",
+    srcs: ["power.stats-default.xml"],
+}
+
+filegroup {
+    name: "android.hardware.power.stats.rc",
+    srcs: ["power.stats-default.rc"],
+}
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
index d3f0cf9..44f9865 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
@@ -606,7 +606,8 @@
     if (radioRsp_v1_6->rspInfo.error == ::android::hardware::radio::V1_6::RadioError::NONE) {
         /* Wait some time for setting sim power down and then verify it */
         updateSimCardStatus();
-        EXPECT_EQ(CardState::PRESENT, cardStatus.base.base.base.cardState);
+        // We cannot assert the consistency of CardState here due to b/203031664
+        // EXPECT_EQ(CardState::PRESENT, cardStatus.base.base.base.cardState);
         // applications should be an empty vector of AppStatus
         EXPECT_EQ(0, cardStatus.applications.size());
     }
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 71b1765..d135a69 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
@@ -43,7 +43,7 @@
   oneway void imsNetworkStateChanged(in android.hardware.radio.RadioIndicationType type);
   oneway void networkScanResult(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.network.NetworkScanResult result);
   oneway void networkStateChanged(in android.hardware.radio.RadioIndicationType type);
-  oneway void nitzTimeReceived(in android.hardware.radio.RadioIndicationType type, in String nitzTime, in long receivedTime);
+  oneway void nitzTimeReceived(in android.hardware.radio.RadioIndicationType type, in String nitzTime, in long receivedTimeMs, in long ageMs);
   oneway void registrationFailed(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.network.CellIdentity cellIdentity, in String chosenPlmn, in android.hardware.radio.network.Domain domain, in int causeCode, in int additionalCauseCode);
   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);
diff --git a/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl b/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
index a2fac20..ba7610d 100644
--- a/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
+++ b/radio/aidl/android/hardware/radio/network/IRadioNetworkIndication.aidl
@@ -129,9 +129,15 @@
      *
      * @param type Type of radio indication
      * @param nitzTime NITZ time string in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
-     * @param receivedTime milliseconds since boot that the NITZ time was received
+     * @param receivedTimeMs time (in milliseconds since boot) at which RIL sent the NITZ time to
+     *        the framework
+     * @param ageMs time in milliseconds indicating how long NITZ was cached in RIL and modem.
+     *        This must track true age and therefore must be calculated using clocks that
+     *        include the time spend in sleep / low power states. If it can not be guaranteed,
+     *        there must not be any caching done at the modem and should fill in 0 for ageMs
      */
-    void nitzTimeReceived(in RadioIndicationType type, in String nitzTime, in long receivedTime);
+    void nitzTimeReceived(in RadioIndicationType type, in String nitzTime,
+            in long receivedTimeMs, in long ageMs);
 
     /**
      * Report that Registration or a Location/Routing/Tracking Area update has failed.
diff --git a/security/keymint/aidl/OWNERS b/security/OWNERS
similarity index 100%
rename from security/keymint/aidl/OWNERS
rename to security/OWNERS
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 37acfa9..12ce859 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -1307,7 +1307,8 @@
                                AuthorizationSet expected_sw_enforced,  //
                                AuthorizationSet expected_hw_enforced,  //
                                SecurityLevel security_level,
-                               const vector<uint8_t>& attestation_cert) {
+                               const vector<uint8_t>& attestation_cert,
+                               vector<uint8_t>* unique_id) {
     X509_Ptr cert(parse_cert_blob(attestation_cert));
     EXPECT_TRUE(!!cert.get());
     if (!cert.get()) return false;
@@ -1472,6 +1473,10 @@
     expected_hw_enforced.Sort();
     EXPECT_EQ(filtered_tags(expected_hw_enforced), filtered_tags(att_hw_enforced));
 
+    if (unique_id != nullptr) {
+        *unique_id = att_unique_id;
+    }
+
     return true;
 }
 
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
index ec3fcf6..7b3b9d4 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h
@@ -338,7 +338,8 @@
                                AuthorizationSet expected_sw_enforced,  //
                                AuthorizationSet expected_hw_enforced,  //
                                SecurityLevel security_level,
-                               const vector<uint8_t>& attestation_cert);
+                               const vector<uint8_t>& attestation_cert,
+                               vector<uint8_t>* unique_id = nullptr);
 
 string bin2hex(const vector<uint8_t>& data);
 X509_Ptr parse_cert_blob(const vector<uint8_t>& blob);
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index 53d980d..95e25d6 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -1621,6 +1621,94 @@
 }
 
 /*
+ * NewKeyGenerationTest.EcdsaAttestationUniqueId
+ *
+ * Verifies that creation of an attested ECDSA key with a UNIQUE_ID included.
+ */
+TEST_P(NewKeyGenerationTest, EcdsaAttestationUniqueId) {
+    auto get_unique_id = [this](const std::string& app_id, uint64_t datetime,
+                                vector<uint8_t>* unique_id) {
+        auto challenge = "hello";
+        auto subject = "cert subj 2";
+        vector<uint8_t> subject_der(make_name_from_str(subject));
+        uint64_t serial_int = 0x1010;
+        vector<uint8_t> serial_blob(build_serial_blob(serial_int));
+        const AuthorizationSetBuilder builder =
+                AuthorizationSetBuilder()
+                        .Authorization(TAG_NO_AUTH_REQUIRED)
+                        .Authorization(TAG_INCLUDE_UNIQUE_ID)
+                        .EcdsaSigningKey(EcCurve::P_256)
+                        .Digest(Digest::NONE)
+                        .AttestationChallenge(challenge)
+                        .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob)
+                        .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der)
+                        .AttestationApplicationId(app_id)
+                        .Authorization(TAG_CREATION_DATETIME, datetime)
+                        .SetDefaultValidity();
+
+        ASSERT_EQ(ErrorCode::OK, GenerateKey(builder));
+        ASSERT_GT(key_blob_.size(), 0U);
+
+        EXPECT_TRUE(ChainSignaturesAreValid(cert_chain_));
+        ASSERT_GT(cert_chain_.size(), 0);
+        verify_subject_and_serial(cert_chain_[0], serial_int, subject, /* self_signed = */ false);
+
+        AuthorizationSet hw_enforced = HwEnforcedAuthorizations(key_characteristics_);
+        AuthorizationSet sw_enforced = SwEnforcedAuthorizations(key_characteristics_);
+
+        // Check that the unique ID field in the extension is non-empty.
+        EXPECT_TRUE(verify_attestation_record(challenge, app_id, sw_enforced, hw_enforced,
+                                              SecLevel(), cert_chain_[0].encodedCertificate,
+                                              unique_id));
+        EXPECT_GT(unique_id->size(), 0);
+        CheckedDeleteKey();
+    };
+
+    // Generate unique ID
+    auto app_id = "foo";
+    uint64_t cert_date = 1619621648000;  // Wed Apr 28 14:54:08 2021 in ms since epoch
+    vector<uint8_t> unique_id;
+    get_unique_id(app_id, cert_date, &unique_id);
+
+    // Generating a new key with the same parameters should give the same unique ID.
+    vector<uint8_t> unique_id2;
+    get_unique_id(app_id, cert_date, &unique_id2);
+    EXPECT_EQ(unique_id, unique_id2);
+
+    // Generating a new key with a slightly different date should give the same unique ID.
+    uint64_t rounded_date = cert_date / 2592000000LLU;
+    uint64_t min_date = rounded_date * 2592000000LLU;
+    uint64_t max_date = ((rounded_date + 1) * 2592000000LLU) - 1;
+
+    vector<uint8_t> unique_id3;
+    get_unique_id(app_id, min_date, &unique_id3);
+    EXPECT_EQ(unique_id, unique_id3);
+
+    vector<uint8_t> unique_id4;
+    get_unique_id(app_id, max_date, &unique_id4);
+    EXPECT_EQ(unique_id, unique_id4);
+
+    // A different attestation application ID should yield a different unique ID.
+    auto app_id2 = "different_foo";
+    vector<uint8_t> unique_id5;
+    get_unique_id(app_id2, cert_date, &unique_id5);
+    EXPECT_NE(unique_id, unique_id5);
+
+    // A radically different date should yield a different unique ID.
+    vector<uint8_t> unique_id6;
+    get_unique_id(app_id, 1611621648000, &unique_id6);
+    EXPECT_NE(unique_id, unique_id6);
+
+    vector<uint8_t> unique_id7;
+    get_unique_id(app_id, max_date + 1, &unique_id7);
+    EXPECT_NE(unique_id, unique_id7);
+
+    vector<uint8_t> unique_id8;
+    get_unique_id(app_id, min_date - 1, &unique_id8);
+    EXPECT_NE(unique_id, unique_id8);
+}
+
+/*
  * NewKeyGenerationTest.EcdsaAttestationTagNoApplicationId
  *
  * Verifies that creation of an attested ECDSA key does not include APPLICATION_ID.
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index 38f3586..76fb79b 100644
--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -222,7 +222,7 @@
     // Generate an ECDSA key that is attested by the generated P256 keypair.
     AuthorizationSet keyDesc = AuthorizationSetBuilder()
                                        .Authorization(TAG_NO_AUTH_REQUIRED)
-                                       .EcdsaSigningKey(256)
+                                       .EcdsaSigningKey(EcCurve::P_256)
                                        .AttestationChallenge("foo")
                                        .AttestationApplicationId("bar")
                                        .Digest(Digest::NONE)
diff --git a/security/keymint/support/OWNERS b/security/keymint/support/OWNERS
deleted file mode 100644
index a93b171..0000000
--- a/security/keymint/support/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-jbires@google.com
-jdanis@google.com
-seleneh@google.com
-swillden@google.com
diff --git a/security/keymint/support/authorization_set.cpp b/security/keymint/support/authorization_set.cpp
index 25eace3..c1b5d48 100644
--- a/security/keymint/support/authorization_set.cpp
+++ b/security/keymint/support/authorization_set.cpp
@@ -161,11 +161,6 @@
     return EncryptionKey();
 }
 
-AuthorizationSetBuilder& AuthorizationSetBuilder::EcdsaSigningKey(uint32_t key_size) {
-    EcdsaKey(key_size);
-    return SigningKey();
-}
-
 AuthorizationSetBuilder& AuthorizationSetBuilder::EcdsaSigningKey(EcCurve curve) {
     EcdsaKey(curve);
     return SigningKey();
diff --git a/security/keymint/support/include/keymint_support/authorization_set.h b/security/keymint/support/include/keymint_support/authorization_set.h
index ca51b08..e41a329 100644
--- a/security/keymint/support/include/keymint_support/authorization_set.h
+++ b/security/keymint/support/include/keymint_support/authorization_set.h
@@ -281,7 +281,6 @@
 
     AuthorizationSetBuilder& RsaSigningKey(uint32_t key_size, uint64_t public_exponent);
     AuthorizationSetBuilder& RsaEncryptionKey(uint32_t key_size, uint64_t public_exponent);
-    AuthorizationSetBuilder& EcdsaSigningKey(uint32_t key_size);
     AuthorizationSetBuilder& EcdsaSigningKey(EcCurve curve);
     AuthorizationSetBuilder& AesEncryptionKey(uint32_t key_size);
     AuthorizationSetBuilder& TripleDesEncryptionKey(uint32_t key_size);
diff --git a/security/secureclock/aidl/OWNERS b/security/secureclock/aidl/OWNERS
deleted file mode 100644
index a93b171..0000000
--- a/security/secureclock/aidl/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-jbires@google.com
-jdanis@google.com
-seleneh@google.com
-swillden@google.com
diff --git a/security/sharedsecret/aidl/OWNERS b/security/sharedsecret/aidl/OWNERS
deleted file mode 100644
index a93b171..0000000
--- a/security/sharedsecret/aidl/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-jbires@google.com
-jdanis@google.com
-seleneh@google.com
-swillden@google.com
diff --git a/sensors/1.0/vts/functional/OWNERS b/sensors/1.0/vts/functional/OWNERS
index 892da15..e20125b 100644
--- a/sensors/1.0/vts/functional/OWNERS
+++ b/sensors/1.0/vts/functional/OWNERS
@@ -1,8 +1,2 @@
-# Sensors team
-arthuri@google.com
-bduddie@google.com
-stange@google.com
-
-# VTS team
-trong@google.com
-yim@google.com
+# Bug component: 62965
+include ../../../common/vts/OWNERS
diff --git a/sensors/2.0/vts/functional/OWNERS b/sensors/2.0/vts/functional/OWNERS
index 892da15..e20125b 100644
--- a/sensors/2.0/vts/functional/OWNERS
+++ b/sensors/2.0/vts/functional/OWNERS
@@ -1,8 +1,2 @@
-# Sensors team
-arthuri@google.com
-bduddie@google.com
-stange@google.com
-
-# VTS team
-trong@google.com
-yim@google.com
+# Bug component: 62965
+include ../../../common/vts/OWNERS
diff --git a/sensors/2.1/vts/functional/OWNERS b/sensors/2.1/vts/functional/OWNERS
index 892da15..e20125b 100644
--- a/sensors/2.1/vts/functional/OWNERS
+++ b/sensors/2.1/vts/functional/OWNERS
@@ -1,8 +1,2 @@
-# Sensors team
-arthuri@google.com
-bduddie@google.com
-stange@google.com
-
-# VTS team
-trong@google.com
-yim@google.com
+# Bug component: 62965
+include ../../../common/vts/OWNERS
diff --git a/tv/cec/1.0/default/Android.bp b/tv/cec/1.0/default/Android.bp
index b4053df..e4c226d 100644
--- a/tv/cec/1.0/default/Android.bp
+++ b/tv/cec/1.0/default/Android.bp
@@ -15,6 +15,7 @@
     srcs: [
         "HdmiCec.cpp",
         "HdmiCecDefault.cpp",
+        "HdmiCecPort.cpp",
     ],
 
     shared_libs: [
diff --git a/tv/cec/1.0/default/HdmiCecDefault.cpp b/tv/cec/1.0/default/HdmiCecDefault.cpp
index 299bcf0..26ccb7d 100644
--- a/tv/cec/1.0/default/HdmiCecDefault.cpp
+++ b/tv/cec/1.0/default/HdmiCecDefault.cpp
@@ -16,19 +16,22 @@
 
 #define LOG_TAG "android.hardware.tv.cec@1.0-impl"
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 
 #include <cutils/properties.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <linux/cec.h>
 #include <linux/ioctl.h>
 #include <poll.h>
-#include <pthread.h>
-#include <sys/eventfd.h>
-#include <algorithm>
 
 #include "HdmiCecDefault.h"
 
+#define PROPERTY_DEVICE_TYPE "ro.hdmi.device_type"
+#define MIN_PORT_ID 0
+#define MAX_PORT_ID 15
+#define INVALID_PHYSICAL_ADDRESS 0xFFFF
+
 namespace android {
 namespace hardware {
 namespace tv {
@@ -36,24 +39,14 @@
 namespace V1_0 {
 namespace implementation {
 
-// When set to false, all the CEC commands are discarded. True by default after initialization.
-bool mCecEnabled;
-/*
- * When set to false, HAL does not wake up the system upon receiving <Image View On> or
- * <Text View On>. True by default after initialization.
- */
-bool mWakeupEnabled;
-
-int mCecFd;
-int mExitFd;
-pthread_t mEventThread;
-sp<IHdmiCecCallback> mCallback;
+using android::base::GetUintProperty;
+using std::stoi;
+using std::string;
 
 HdmiCecDefault::HdmiCecDefault() {
-    mCecFd = -1;
-    mExitFd = -1;
     mCecEnabled = false;
     mWakeupEnabled = false;
+    mCecControlEnabled = false;
     mCallback = nullptr;
 }
 
@@ -68,8 +61,8 @@
         return Result::FAILURE_INVALID_ARGS;
     }
 
-    struct cec_log_addrs cecLogAddrs;
-    int ret = ioctl(mCecFd, CEC_ADAP_G_LOG_ADDRS, &cecLogAddrs);
+    cec_log_addrs cecLogAddrs;
+    int ret = ioctl(mHdmiCecPorts[MIN_PORT_ID]->mCecFd, CEC_ADAP_G_LOG_ADDRS, &cecLogAddrs);
     if (ret) {
         LOG(ERROR) << "Add logical address failed, Error = " << strerror(errno);
         return Result::FAILURE_BUSY;
@@ -135,27 +128,36 @@
     cecLogAddrs.features[logAddrIndex][0] = 0;
     cecLogAddrs.features[logAddrIndex][1] = 0;
 
-    ret = ioctl(mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
-    if (ret) {
-        LOG(ERROR) << "Add logical address failed, Error = " << strerror(errno);
-        return Result::FAILURE_BUSY;
+    // Return failure only if add logical address fails for all the ports
+    Return<Result> result = Result::FAILURE_BUSY;
+    for (int i = 0; i < mHdmiCecPorts.size(); i++) {
+        ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
+        if (ret) {
+            LOG(ERROR) << "Add logical address failed for port " << mHdmiCecPorts[i]->mPortId
+                       << ", Error = " << strerror(errno);
+        } else {
+            result = Result::SUCCESS;
+        }
     }
-    return Result::SUCCESS;
+    return result;
 }
 
 Return<void> HdmiCecDefault::clearLogicalAddress() {
-    struct cec_log_addrs cecLogAddrs;
+    cec_log_addrs cecLogAddrs;
     memset(&cecLogAddrs, 0, sizeof(cecLogAddrs));
-    int ret = ioctl(mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
-    if (ret) {
-        LOG(ERROR) << "Clear logical Address failed, Error = " << strerror(errno);
+    for (int i = 0; i < mHdmiCecPorts.size(); i++) {
+        int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
+        if (ret) {
+            LOG(ERROR) << "Clear logical Address failed for port " << mHdmiCecPorts[i]->mPortId
+                       << ", Error = " << strerror(errno);
+        }
     }
     return Void();
 }
 
 Return<void> HdmiCecDefault::getPhysicalAddress(getPhysicalAddress_cb callback) {
     uint16_t addr;
-    int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
+    int ret = ioctl(mHdmiCecPorts[MIN_PORT_ID]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
     if (ret) {
         LOG(ERROR) << "Get physical address failed, Error = " << strerror(errno);
         callback(Result::FAILURE_INVALID_STATE, addr);
@@ -170,7 +172,7 @@
         return SendMessageResult::FAIL;
     }
 
-    struct cec_msg cecMsg;
+    cec_msg cecMsg;
     memset(&cecMsg, 0, sizeof(cec_msg));
 
     int initiator = static_cast<cec_logical_address_t>(message.initiator);
@@ -182,27 +184,25 @@
     }
     cecMsg.len = message.body.size() + 1;
 
-    int ret = ioctl(mCecFd, CEC_TRANSMIT, &cecMsg);
+    // Return failure only if send message fails for all the ports
+    Return<SendMessageResult> result = SendMessageResult::FAIL;
+    for (int i = 0; i < mHdmiCecPorts.size(); i++) {
+        int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_TRANSMIT, &cecMsg);
 
-    if (ret) {
-        LOG(ERROR) << "Send message failed, Error = " << strerror(errno);
-        return SendMessageResult::FAIL;
-    }
+        if (ret) {
+            LOG(ERROR) << "Send message failed, Error = " << strerror(errno);
+            continue;
+        }
 
-    if (cecMsg.tx_status != CEC_TX_STATUS_OK) {
-        LOG(ERROR) << "Send message tx_status = " << cecMsg.tx_status;
-    }
+        if (cecMsg.tx_status != CEC_TX_STATUS_OK) {
+            LOG(ERROR) << "Send message tx_status = " << cecMsg.tx_status;
+        }
 
-    switch (cecMsg.tx_status) {
-        case CEC_TX_STATUS_OK:
-            return SendMessageResult::SUCCESS;
-        case CEC_TX_STATUS_ARB_LOST:
-            return SendMessageResult::BUSY;
-        case CEC_TX_STATUS_NACK:
-            return SendMessageResult::NACK;
-        default:
-            return SendMessageResult::FAIL;
+        if (result != SendMessageResult::SUCCESS) {
+            result = getSendMessageResult(cecMsg.tx_status);
+        }
     }
+    return result;
 }
 
 Return<void> HdmiCecDefault::setCallback(const sp<IHdmiCecCallback>& callback) {
@@ -227,19 +227,25 @@
 }
 
 Return<void> HdmiCecDefault::getPortInfo(getPortInfo_cb callback) {
-    uint16_t addr;
-    int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
-    if (ret) {
-        LOG(ERROR) << "Get port info failed, Error = " << strerror(errno);
+    hidl_vec<HdmiPortInfo> portInfos(mHdmiCecPorts.size());
+    for (int i = 0; i < mHdmiCecPorts.size(); i++) {
+        uint16_t addr = INVALID_PHYSICAL_ADDRESS;
+        int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
+        if (ret) {
+            LOG(ERROR) << "Get port info failed for port : " << mHdmiCecPorts[i]->mPortId
+                       << ", Error = " << strerror(errno);
+        }
+        HdmiPortType type = HdmiPortType::INPUT;
+        uint32_t deviceType = GetUintProperty<uint32_t>(PROPERTY_DEVICE_TYPE, CEC_DEVICE_PLAYBACK);
+        if (deviceType != CEC_DEVICE_TV && i == MIN_PORT_ID) {
+            type = HdmiPortType::OUTPUT;
+        }
+        portInfos[i] = {.type = type,
+                        .portId = mHdmiCecPorts[i]->mPortId,
+                        .cecSupported = true,
+                        .arcSupported = false,
+                        .physicalAddress = addr};
     }
-
-    unsigned int type = property_get_int32("ro.hdmi.device_type", CEC_DEVICE_PLAYBACK);
-    hidl_vec<HdmiPortInfo> portInfos(1);
-    portInfos[0] = {.type = (type == CEC_DEVICE_TV ? HdmiPortType::INPUT : HdmiPortType::OUTPUT),
-                    .portId = 1,
-                    .cecSupported = true,
-                    .arcSupported = false,
-                    .physicalAddress = addr};
     callback(portInfos);
     return Void();
 }
@@ -254,7 +260,9 @@
             LOG(DEBUG) << "setOption: WAKEUP: " << value;
             mWakeupEnabled = value;
             break;
-        default:
+        case OptionKey::SYSTEM_CEC_CONTROL:
+            LOG(DEBUG) << "setOption: SYSTEM_CEC_CONTROL: " << value;
+            mCecControlEnabled = value;
             break;
     }
     return Void();
@@ -268,9 +276,9 @@
     return Void();
 }
 
-Return<bool> HdmiCecDefault::isConnected(int32_t /*portId*/) {
+Return<bool> HdmiCecDefault::isConnected(int32_t portId) {
     uint16_t addr;
-    int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
+    int ret = ioctl(mHdmiCecPorts[portId]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
     if (ret) {
         LOG(ERROR) << "Is connected failed, Error = " << strerror(errno);
         return false;
@@ -281,79 +289,70 @@
     return true;
 }
 
-// Initialise the cec file descriptor
+int getPortId(string cecFilename) {
+    int portId = stoi(cecFilename.substr(3));
+    if (portId >= MIN_PORT_ID && portId <= MAX_PORT_ID) {
+        return portId;
+    } else {
+        return -1;
+    }
+}
+
+// Initialise the cec file descriptors
 Return<Result> HdmiCecDefault::init() {
-    const char* path = "/dev/cec0";
-    mCecFd = open(path, O_RDWR);
-    if (mCecFd < 0) {
-        LOG(ERROR) << "Failed to open " << path << ", Error = " << strerror(errno);
-        return Result::FAILURE_NOT_SUPPORTED;
-    }
-    mExitFd = eventfd(0, EFD_NONBLOCK);
-    if (mExitFd < 0) {
-        LOG(ERROR) << "Failed to open eventfd, Error = " << strerror(errno);
-        release();
-        return Result::FAILURE_NOT_SUPPORTED;
+    const char* parentPath = "/dev/";
+    DIR* dir = opendir(parentPath);
+    const char* cecFilename = "cec";
+
+    while (struct dirent* dirEntry = readdir(dir)) {
+        string filename = dirEntry->d_name;
+        if (filename.compare(0, 3, cecFilename, 0, 3) == 0) {
+            int portId = getPortId(filename);
+            if (portId == -1) {
+                continue;
+            }
+            shared_ptr<HdmiCecPort> hdmiCecPort(new HdmiCecPort(portId));
+            string filepath = parentPath + filename;
+            Result result = hdmiCecPort->init(filepath.c_str());
+            if (result != Result::SUCCESS) {
+                continue;
+            }
+            thread eventThread(&HdmiCecDefault::event_thread, this, hdmiCecPort.get());
+            mEventThreads.push_back(std::move(eventThread));
+            mHdmiCecPorts.push_back(std::move(hdmiCecPort));
+        }
     }
 
-    // Ensure the CEC device supports required capabilities
-    struct cec_caps caps = {};
-    int ret = ioctl(mCecFd, CEC_ADAP_G_CAPS, &caps);
-    if (ret) {
-        LOG(ERROR) << "Unable to query cec adapter capabilities, Error = " << strerror(errno);
-        release();
-        return Result::FAILURE_NOT_SUPPORTED;
-    }
-
-    if (!(caps.capabilities & (CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH))) {
-        LOG(ERROR) << "Wrong cec adapter capabilities " << caps.capabilities;
-        release();
-        return Result::FAILURE_NOT_SUPPORTED;
-    }
-
-    uint32_t mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
-    ret = ioctl(mCecFd, CEC_S_MODE, &mode);
-    if (ret) {
-        LOG(ERROR) << "Unable to set initiator mode, Error = " << strerror(errno);
-        release();
-        return Result::FAILURE_NOT_SUPPORTED;
-    }
-
-    /* thread loop for receiving cec messages and hotplug events*/
-    if (pthread_create(&mEventThread, NULL, event_thread, NULL)) {
-        LOG(ERROR) << "Can't create event thread: " << strerror(errno);
-        release();
+    if (mHdmiCecPorts.empty()) {
         return Result::FAILURE_NOT_SUPPORTED;
     }
 
     mCecEnabled = true;
     mWakeupEnabled = true;
+    mCecControlEnabled = true;
     return Result::SUCCESS;
 }
 
 Return<void> HdmiCecDefault::release() {
-    if (mExitFd > 0) {
-        uint64_t tmp = 1;
-        write(mExitFd, &tmp, sizeof(tmp));
-        pthread_join(mEventThread, NULL);
-    }
-    if (mExitFd > 0) {
-        close(mExitFd);
-    }
-    if (mCecFd > 0) {
-        close(mCecFd);
-    }
     mCecEnabled = false;
     mWakeupEnabled = false;
+    mCecControlEnabled = false;
+    for (thread& eventThread : mEventThreads) {
+        if (eventThread.joinable()) {
+            eventThread.join();
+        }
+    }
     setCallback(nullptr);
+    mHdmiCecPorts.clear();
+    mEventThreads.clear();
     return Void();
 }
 
-void* HdmiCecDefault::event_thread(void*) {
+void HdmiCecDefault::event_thread(HdmiCecPort* hdmiCecPort) {
     struct pollfd ufds[3] = {
-            {mCecFd, POLLIN, 0},
-            {mCecFd, POLLERR, 0},
-            {mExitFd, POLLIN, 0},
+            {hdmiCecPort->mCecFd, POLLIN, 0},
+            {hdmiCecPort->mCecFd, POLLERR, 0},
+            {hdmiCecPort->mExitFd, POLLIN, 0},
     };
 
     while (1) {
@@ -372,23 +371,23 @@
         }
 
         if (ufds[1].revents == POLLERR) { /* CEC Event */
-            struct cec_event ev;
-            ret = ioctl(mCecFd, CEC_DQEVENT, &ev);
-
-            if (!mCecEnabled) {
-                continue;
-            }
+            cec_event ev;
+            ret = ioctl(hdmiCecPort->mCecFd, CEC_DQEVENT, &ev);
 
             if (ret) {
                 LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno);
                 continue;
             }
 
+            if (!mCecEnabled) {
+                continue;
+            }
+
             if (ev.event == CEC_EVENT_STATE_CHANGE) {
                 if (mCallback != nullptr) {
                     HotplugEvent hotplugEvent{
                             .connected = (ev.state_change.phys_addr != CEC_PHYS_ADDR_INVALID),
-                            .portId = 1};
+                            .portId = hdmiCecPort->mPortId};
                     mCallback->onHotplugEvent(hotplugEvent);
                 } else {
                     LOG(ERROR) << "No event callback for hotplug";
@@ -397,12 +396,8 @@
         }
 
         if (ufds[0].revents == POLLIN) { /* CEC Driver */
-            struct cec_msg msg = {};
-            ret = ioctl(mCecFd, CEC_RECEIVE, &msg);
-
-            if (!mCecEnabled) {
-                continue;
-            }
+            cec_msg msg = {};
+            ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg);
 
             if (ret) {
                 LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno);
@@ -414,11 +409,20 @@
                 continue;
             }
 
+            if (!mCecEnabled) {
+                continue;
+            }
+
             if (!mWakeupEnabled && isWakeupMessage(msg)) {
                 LOG(DEBUG) << "Filter wakeup message";
                 continue;
             }
 
+            if (!mCecControlEnabled && !isTransferableInSleep(msg)) {
+                LOG(DEBUG) << "Filter message in standby mode";
+                continue;
+            }
+
             if (mCallback != nullptr) {
                 size_t length = std::min(msg.len - 1, (uint32_t)MaxLength::MESSAGE_BODY);
                 CecMessage cecMessage{
@@ -435,14 +439,13 @@
             }
         }
     }
-    return NULL;
 }
 
-int HdmiCecDefault::getOpcode(struct cec_msg message) {
-    return (static_cast<uint8_t>(message.msg[1]) & 0xff);
+int HdmiCecDefault::getOpcode(cec_msg message) {
+    return static_cast<uint8_t>(message.msg[1]);
 }
 
-bool HdmiCecDefault::isWakeupMessage(struct cec_msg message) {
+bool HdmiCecDefault::isWakeupMessage(cec_msg message) {
     int opcode = getOpcode(message);
     switch (opcode) {
         case CEC_MESSAGE_TEXT_VIEW_ON:
@@ -453,6 +456,61 @@
     }
 }
 
+bool HdmiCecDefault::isTransferableInSleep(cec_msg message) {
+    int opcode = getOpcode(message);
+    switch (opcode) {
+        case CEC_MESSAGE_ABORT:
+        case CEC_MESSAGE_DEVICE_VENDOR_ID:
+        case CEC_MESSAGE_GET_CEC_VERSION:
+        case CEC_MESSAGE_GET_MENU_LANGUAGE:
+        case CEC_MESSAGE_GIVE_DEVICE_POWER_STATUS:
+        case CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID:
+        case CEC_MESSAGE_GIVE_OSD_NAME:
+        case CEC_MESSAGE_GIVE_PHYSICAL_ADDRESS:
+        case CEC_MESSAGE_REPORT_PHYSICAL_ADDRESS:
+        case CEC_MESSAGE_REPORT_POWER_STATUS:
+        case CEC_MESSAGE_SET_OSD_NAME:
+        case CEC_MESSAGE_DECK_CONTROL:
+        case CEC_MESSAGE_PLAY:
+        case CEC_MESSAGE_IMAGE_VIEW_ON:
+        case CEC_MESSAGE_TEXT_VIEW_ON:
+        case CEC_MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
+            return true;
+        case CEC_MESSAGE_USER_CONTROL_PRESSED:
+            return isPowerUICommand(message);
+        default:
+            return false;
+    }
+}
+
+int HdmiCecDefault::getFirstParam(cec_msg message) {
+    return static_cast<uint8_t>(message.msg[2]);
+}
+
+bool HdmiCecDefault::isPowerUICommand(cec_msg message) {
+    int uiCommand = getFirstParam(message);
+    switch (uiCommand) {
+        case CEC_OP_UI_CMD_POWER:
+        case CEC_OP_UI_CMD_DEVICE_ROOT_MENU:
+        case CEC_OP_UI_CMD_POWER_ON_FUNCTION:
+            return true;
+        default:
+            return false;
+    }
+}
+
+Return<SendMessageResult> HdmiCecDefault::getSendMessageResult(int tx_status) {
+    switch (tx_status) {
+        case CEC_TX_STATUS_OK:
+            return SendMessageResult::SUCCESS;
+        case CEC_TX_STATUS_ARB_LOST:
+            return SendMessageResult::BUSY;
+        case CEC_TX_STATUS_NACK:
+            return SendMessageResult::NACK;
+        default:
+            return SendMessageResult::FAIL;
+    }
+}
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace cec
diff --git a/tv/cec/1.0/default/HdmiCecDefault.h b/tv/cec/1.0/default/HdmiCecDefault.h
index c1bb2c7..6574429 100644
--- a/tv/cec/1.0/default/HdmiCecDefault.h
+++ b/tv/cec/1.0/default/HdmiCecDefault.h
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#include <android/hardware/tv/cec/1.0/IHdmiCec.h>
 #include <hardware/hdmi_cec.h>
+#include <linux/cec.h>
+#include <thread>
+#include <vector>
+#include "HdmiCecPort.h"
 
 namespace android {
 namespace hardware {
@@ -24,7 +26,12 @@
 namespace V1_0 {
 namespace implementation {
 
-struct HdmiCecDefault : public IHdmiCec, public hidl_death_recipient {
+using std::shared_ptr;
+using std::thread;
+using std::vector;
+
+class HdmiCecDefault : public IHdmiCec, public hidl_death_recipient {
+  public:
     HdmiCecDefault();
     ~HdmiCecDefault();
     // Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow.
@@ -47,11 +54,35 @@
 
     Return<Result> init();
     Return<void> release();
-    static void* event_thread(void*);
-    static int getOpcode(struct cec_msg message);
-    static bool isWakeupMessage(struct cec_msg message);
-};
 
+  private:
+    void event_thread(HdmiCecPort* hdmiCecPort);
+    static int getOpcode(cec_msg message);
+    static int getFirstParam(cec_msg message);
+    static bool isWakeupMessage(cec_msg message);
+    static bool isTransferableInSleep(cec_msg message);
+    static bool isPowerUICommand(cec_msg message);
+    static Return<SendMessageResult> getSendMessageResult(int tx_status);
+
+    vector<thread> mEventThreads;
+    vector<shared_ptr<HdmiCecPort>> mHdmiCecPorts;
+
+    // When set to false, all the CEC commands are discarded. True by default after initialization.
+    bool mCecEnabled;
+    /*
+     * When set to false, HAL does not wake up the system upon receiving <Image View On> or
+     * <Text View On>. True by default after initialization.
+     */
+    bool mWakeupEnabled;
+    /*
+     * Updated when system goes into or comes out of standby mode.
+     * When set to true, Android system is handling CEC commands.
+     * When set to false, microprocessor is handling CEC commands.
+     * True by default after initialization.
+     */
+    bool mCecControlEnabled;
+    sp<IHdmiCecCallback> mCallback;
+};
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace cec
diff --git a/tv/cec/1.0/default/HdmiCecPort.cpp b/tv/cec/1.0/default/HdmiCecPort.cpp
new file mode 100755
index 0000000..73dda12
--- /dev/null
+++ b/tv/cec/1.0/default/HdmiCecPort.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "android.hardware.tv.cec@1.0-impl"
+
+#include <android-base/logging.h>
+#include <errno.h>
+#include <linux/cec.h>
+#include <linux/ioctl.h>
+#include <sys/eventfd.h>
+#include <algorithm>
+
+#include "HdmiCecPort.h"
+
+namespace android {
+namespace hardware {
+namespace tv {
+namespace cec {
+namespace V1_0 {
+namespace implementation {
+
+HdmiCecPort::HdmiCecPort(unsigned int portId) {
+    mPortId = portId;
+    mCecFd = -1;
+    mExitFd = -1;
+}
+
+HdmiCecPort::~HdmiCecPort() {
+    release();
+}
+
+// Initialise the cec file descriptor
+Return<Result> HdmiCecPort::init(const char* path) {
+    mCecFd = open(path, O_RDWR);
+    if (mCecFd < 0) {
+        LOG(ERROR) << "Failed to open " << path << ", Error = " << strerror(errno);
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+    mExitFd = eventfd(0, EFD_NONBLOCK);
+    if (mExitFd < 0) {
+        LOG(ERROR) << "Failed to open eventfd, Error = " << strerror(errno);
+        release();
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+
+    // Ensure the CEC device supports required capabilities
+    struct cec_caps caps = {};
+    int ret = ioctl(mCecFd, CEC_ADAP_G_CAPS, &caps);
+    if (ret) {
+        LOG(ERROR) << "Unable to query cec adapter capabilities, Error = " << strerror(errno);
+        release();
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+
+    if (!(caps.capabilities & (CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH))) {
+        LOG(ERROR) << "Wrong cec adapter capabilities " << caps.capabilities;
+        release();
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+
+    uint32_t mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
+    ret = ioctl(mCecFd, CEC_S_MODE, &mode);
+    if (ret) {
+        LOG(ERROR) << "Unable to set initiator mode, Error = " << strerror(errno);
+        release();
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+    return Result::SUCCESS;
+}
+
+Return<void> HdmiCecPort::release() {
+    if (mExitFd > 0) {
+        uint64_t tmp = 1;
+        write(mExitFd, &tmp, sizeof(tmp));
+    }
+    if (mExitFd > 0) {
+        close(mExitFd);
+    }
+    if (mCecFd > 0) {
+        close(mCecFd);
+    }
+    return Void();
+}
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace cec
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
diff --git a/tv/cec/1.0/default/HdmiCecPort.h b/tv/cec/1.0/default/HdmiCecPort.h
new file mode 100755
index 0000000..2a2fdef
--- /dev/null
+++ b/tv/cec/1.0/default/HdmiCecPort.h
@@ -0,0 +1,42 @@
+/*
+ * 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/hardware/tv/cec/1.0/IHdmiCec.h>
+
+namespace android {
+namespace hardware {
+namespace tv {
+namespace cec {
+namespace V1_0 {
+namespace implementation {
+
+class HdmiCecPort {
+  public:
+    HdmiCecPort(unsigned int portId);
+    ~HdmiCecPort();
+    Return<Result> init(const char* path);
+    Return<void> release();
+
+    unsigned int mPortId;
+    int mCecFd;
+    int mExitFd;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace cec
+}  // namespace tv
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHint.aidl
similarity index 85%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHint.aidl
index 2b872ab..1fdafd2 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHint.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -31,14 +31,10 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.tv.tuner;
+/* @hide */
+@VintfStability
+parcelable FilterDelayHint {
+  android.hardware.tv.tuner.FilterDelayHintType hintType = android.hardware.tv.tuner.FilterDelayHintType.INVALID;
+  int hintValue;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHintType.aidl
similarity index 87%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHintType.aidl
index 2b872ab..8c5a3f5 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FilterDelayHintType.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -31,14 +31,11 @@
 // 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;
+package android.hardware.tv.tuner;
+/* @hide */
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum FilterDelayHintType {
+  INVALID = 0,
+  TIME_DELAY_IN_MS = 1,
+  DATA_SIZE_DELAY_IN_BYTES = 2,
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendInterleaveMode.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
index 5e03731..ac38731 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
@@ -38,4 +38,5 @@
   android.hardware.tv.tuner.FrontendAtsc3TimeInterleaveMode atsc3 = android.hardware.tv.tuner.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
   android.hardware.tv.tuner.FrontendCableTimeInterleaveMode dvbc;
   android.hardware.tv.tuner.FrontendDtmbTimeInterleaveMode dtmb;
+  android.hardware.tv.tuner.FrontendIsdbtTimeInterleaveMode isdbt;
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
index 097fcb8..dbf631b 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
@@ -40,4 +40,7 @@
   int modulationCap;
   int coderateCap;
   int guardIntervalCap;
+  int timeInterleaveCap;
+  boolean isSegmentAuto;
+  boolean isFullSegment;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl
similarity index 73%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl
index 2b872ab..0055793 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -31,14 +31,12 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.tv.tuner;
+/* @hide */
+@VintfStability
+parcelable FrontendIsdbtLayerSettings {
+  android.hardware.tv.tuner.FrontendIsdbtModulation modulation = android.hardware.tv.tuner.FrontendIsdbtModulation.UNDEFINED;
+  android.hardware.tv.tuner.FrontendIsdbtCoderate coderate = android.hardware.tv.tuner.FrontendIsdbtCoderate.UNDEFINED;
+  android.hardware.tv.tuner.FrontendIsdbtTimeInterleaveMode timeInterleave = android.hardware.tv.tuner.FrontendIsdbtTimeInterleaveMode.UNDEFINED;
+  int numOfSegment;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl
similarity index 87%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl
index 2b872ab..133887f 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -31,14 +31,12 @@
 // 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;
+package android.hardware.tv.tuner;
+/* @hide */
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum FrontendIsdbtPartialReceptionFlag {
+  UNDEFINED = 0,
+  AUTO = 1,
+  FALSE = 2,
+  TRUE = 4,
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
index 6249097..605bc21 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
@@ -38,10 +38,10 @@
   long frequency;
   long endFrequency;
   android.hardware.tv.tuner.FrontendSpectralInversion inversion = android.hardware.tv.tuner.FrontendSpectralInversion.UNDEFINED;
-  android.hardware.tv.tuner.FrontendIsdbtModulation modulation = android.hardware.tv.tuner.FrontendIsdbtModulation.UNDEFINED;
   android.hardware.tv.tuner.FrontendIsdbtBandwidth bandwidth = android.hardware.tv.tuner.FrontendIsdbtBandwidth.UNDEFINED;
   android.hardware.tv.tuner.FrontendIsdbtMode mode = android.hardware.tv.tuner.FrontendIsdbtMode.UNDEFINED;
-  android.hardware.tv.tuner.FrontendIsdbtCoderate coderate = android.hardware.tv.tuner.FrontendIsdbtCoderate.UNDEFINED;
   android.hardware.tv.tuner.FrontendIsdbtGuardInterval guardInterval = android.hardware.tv.tuner.FrontendIsdbtGuardInterval.UNDEFINED;
   int serviceAreaId;
+  android.hardware.tv.tuner.FrontendIsdbtPartialReceptionFlag partialReceptionFlag = android.hardware.tv.tuner.FrontendIsdbtPartialReceptionFlag.UNDEFINED;
+  android.hardware.tv.tuner.FrontendIsdbtLayerSettings[] layerSettings;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl
similarity index 78%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl
index 2b872ab..50adde9 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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.
@@ -31,14 +31,22 @@
 // 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;
+package android.hardware.tv.tuner;
+/* @hide */
 @Backing(type="int") @VintfStability
-enum UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+enum FrontendIsdbtTimeInterleaveMode {
+  UNDEFINED = 0,
+  AUTO = 1,
+  INTERLEAVE_1_0 = 2,
+  INTERLEAVE_1_4 = 4,
+  INTERLEAVE_1_8 = 8,
+  INTERLEAVE_1_16 = 16,
+  INTERLEAVE_2_0 = 32,
+  INTERLEAVE_2_2 = 64,
+  INTERLEAVE_2_4 = 128,
+  INTERLEAVE_2_8 = 256,
+  INTERLEAVE_3_0 = 512,
+  INTERLEAVE_3_1 = 1024,
+  INTERLEAVE_3_2 = 2048,
+  INTERLEAVE_3_4 = 4096,
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatus.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatus.aidl
index 6296cfc..7677221 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatus.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatus.aidl
@@ -72,4 +72,6 @@
   boolean isMiso;
   boolean isLinear;
   boolean isShortFrames;
+  android.hardware.tv.tuner.FrontendIsdbtMode isdbtMode;
+  android.hardware.tv.tuner.FrontendIsdbtPartialReceptionFlag partialReceptionFlag;
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatusType.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatusType.aidl
index 7b8bc47..6342479 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatusType.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/FrontendStatusType.aidl
@@ -72,4 +72,6 @@
   IS_MISO = 34,
   IS_LINEAR = 35,
   IS_SHORT_FRAMES = 36,
+  ISDBT_MODE = 37,
+  ISDBT_PARTIAL_RECEPTION_FLAG = 38,
 }
diff --git a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/IFilter.aidl b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/IFilter.aidl
index a0454f4..32d9d64 100644
--- a/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/IFilter.aidl
+++ b/tv/tuner/aidl/aidl_api/android.hardware.tv.tuner/current/android/hardware/tv/tuner/IFilter.aidl
@@ -49,4 +49,5 @@
   long getId64Bit();
   void releaseAvHandle(in android.hardware.common.NativeHandle avMemory, in long avDataId);
   void setDataSource(in android.hardware.tv.tuner.IFilter filter);
+  void setDelayHint(in android.hardware.tv.tuner.FilterDelayHint hint);
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHint.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHint.aidl
new file mode 100644
index 0000000..3f1ca35
--- /dev/null
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHint.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.tv.tuner;
+
+import android.hardware.tv.tuner.FilterDelayHintType;
+
+/**
+ * Filter Delay Hint
+ * Gives information to the filter to assist in delaying / accumulating filter events.
+ * See FilterDelayHintType for more information regarding the hintValue units.
+ * Note: this hint is not valid for media filters.
+ * @hide
+ */
+@VintfStability
+parcelable FilterDelayHint {
+    FilterDelayHintType hintType = FilterDelayHintType.INVALID;
+    int hintValue;
+}
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHintType.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHintType.aidl
new file mode 100644
index 0000000..f5b1454
--- /dev/null
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FilterDelayHintType.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.tv.tuner;
+
+/**
+ * Filter Delay Hint Type
+ * Specifies the type of filter delay.
+ * @hide
+ */
+@VintfStability
+@Backing(type="int")
+enum FilterDelayHintType {
+    /**
+     * Invalid type to be used as default value.
+     */
+    INVALID,
+
+    /**
+     * Hint that can be used by the filter implementation to make decisions about
+     * delaying the filter callback until a specified amount of time has passed.
+     * For time delays, the hint value contains time in milliseconds.
+     */
+    TIME_DELAY_IN_MS,
+
+    /**
+     * Hint that can be used by the filter implementation to make decisions about
+     * delaying the filter callback until a specified amount of data has been
+     * accumulated.
+     * For data size delays, the hint value contains the data size in bytes.
+     */
+    DATA_SIZE_DELAY_IN_BYTES,
+}
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendInterleaveMode.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
index 3aa17dd..106ef67 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendInterleaveMode.aidl
@@ -19,6 +19,7 @@
 import android.hardware.tv.tuner.FrontendAtsc3TimeInterleaveMode;
 import android.hardware.tv.tuner.FrontendCableTimeInterleaveMode;
 import android.hardware.tv.tuner.FrontendDtmbTimeInterleaveMode;
+import android.hardware.tv.tuner.FrontendIsdbtTimeInterleaveMode;
 
 /**
  * @hide
@@ -31,4 +32,6 @@
     FrontendCableTimeInterleaveMode dvbc;
 
     FrontendDtmbTimeInterleaveMode dtmb;
+
+    FrontendIsdbtTimeInterleaveMode isdbt;
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
index 4b764ad..b2bdfd4 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtCapabilities.aidl
@@ -46,4 +46,19 @@
      * Guard Interval Types defined by FrontendIsdbtGuardInterval.
      */
     int guardIntervalCap;
+
+    /**
+     * Time Interleaves defined by FrontendIsdbtTimeInterleaveMode.
+     */
+    int timeInterleaveCap;
+
+    /**
+     * If segment auto Supported or not.
+     */
+    boolean isSegmentAuto;
+
+    /**
+     * If full segment supported or not.
+     */
+    boolean isFullSegment;
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl
new file mode 100644
index 0000000..0b44ecb
--- /dev/null
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtLayerSettings.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.tv.tuner;
+
+import android.hardware.tv.tuner.FrontendIsdbtCoderate;
+import android.hardware.tv.tuner.FrontendIsdbtModulation;
+import android.hardware.tv.tuner.FrontendIsdbtTimeInterleaveMode;
+
+/**
+ * Layer Settings for ISDB-T Frontend.
+ * @hide
+ */
+@VintfStability
+parcelable FrontendIsdbtLayerSettings {
+    FrontendIsdbtModulation modulation = FrontendIsdbtModulation.UNDEFINED;
+
+    FrontendIsdbtCoderate coderate = FrontendIsdbtCoderate.UNDEFINED;
+
+    FrontendIsdbtTimeInterleaveMode timeInterleave = FrontendIsdbtTimeInterleaveMode.UNDEFINED;
+
+    /**
+     * 0 ~ 13 and 0xFF(Auto)
+     */
+    int numOfSegment;
+}
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl
new file mode 100644
index 0000000..81c28cd
--- /dev/null
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtPartialReceptionFlag.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.tv.tuner;
+
+/**
+ * Partial Reception Flag for an ISTB-T Frontend.
+ * @hide
+ */
+@VintfStability
+@Backing(type="int")
+enum FrontendIsdbtPartialReceptionFlag {
+    UNDEFINED = 0,
+
+    /**
+     * Hardware is able to detect and set Partial Reception Flag automatically.
+     */
+    AUTO = 1 << 0,
+
+    FALSE = 1 << 1,
+
+    TRUE = 1 << 2,
+}
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
index 08f6130..23a4769 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtSettings.aidl
@@ -17,10 +17,10 @@
 package android.hardware.tv.tuner;
 
 import android.hardware.tv.tuner.FrontendIsdbtBandwidth;
-import android.hardware.tv.tuner.FrontendIsdbtCoderate;
 import android.hardware.tv.tuner.FrontendIsdbtGuardInterval;
+import android.hardware.tv.tuner.FrontendIsdbtLayerSettings;
 import android.hardware.tv.tuner.FrontendIsdbtMode;
-import android.hardware.tv.tuner.FrontendIsdbtModulation;
+import android.hardware.tv.tuner.FrontendIsdbtPartialReceptionFlag;
 import android.hardware.tv.tuner.FrontendSpectralInversion;
 
 /**
@@ -41,15 +41,15 @@
 
     FrontendSpectralInversion inversion = FrontendSpectralInversion.UNDEFINED;
 
-    FrontendIsdbtModulation modulation = FrontendIsdbtModulation.UNDEFINED;
-
     FrontendIsdbtBandwidth bandwidth = FrontendIsdbtBandwidth.UNDEFINED;
 
     FrontendIsdbtMode mode = FrontendIsdbtMode.UNDEFINED;
 
-    FrontendIsdbtCoderate coderate = FrontendIsdbtCoderate.UNDEFINED;
-
     FrontendIsdbtGuardInterval guardInterval = FrontendIsdbtGuardInterval.UNDEFINED;
 
     int serviceAreaId;
+
+    FrontendIsdbtPartialReceptionFlag partialReceptionFlag = FrontendIsdbtPartialReceptionFlag.UNDEFINED;
+
+    FrontendIsdbtLayerSettings[] layerSettings;
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl
new file mode 100644
index 0000000..6192fca
--- /dev/null
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendIsdbtTimeInterleaveMode.aidl
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.
+ */
+
+package android.hardware.tv.tuner;
+
+/**
+ * Time Interleave Mode for ISDB-T Frontend.
+ * @hide
+ */
+@VintfStability
+@Backing(type="int")
+enum FrontendIsdbtTimeInterleaveMode {
+    UNDEFINED = 0,
+
+    /**
+     * Hardware is able to detect and set Time Interleave Mode automatically.
+     */
+    AUTO = 1 << 0,
+
+    INTERLEAVE_1_0 = 1 << 1,
+
+    INTERLEAVE_1_4 = 1 << 2,
+
+    INTERLEAVE_1_8 = 1 << 3,
+
+    INTERLEAVE_1_16 = 1 << 4,
+
+    INTERLEAVE_2_0 = 1 << 5,
+
+    INTERLEAVE_2_2 = 1 << 6,
+
+    INTERLEAVE_2_4 = 1 << 7,
+
+    INTERLEAVE_2_8 = 1 << 8,
+
+    INTERLEAVE_3_0 = 1 << 9,
+
+    INTERLEAVE_3_1 = 1 << 10,
+
+    INTERLEAVE_3_2 = 1 << 11,
+
+    INTERLEAVE_3_4 = 1 << 12,
+}
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatus.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatus.aidl
index ddbd0f8..9302b76 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatus.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatus.aidl
@@ -21,6 +21,8 @@
 import android.hardware.tv.tuner.FrontendGuardInterval;
 import android.hardware.tv.tuner.FrontendInnerFec;
 import android.hardware.tv.tuner.FrontendInterleaveMode;
+import android.hardware.tv.tuner.FrontendIsdbtMode;
+import android.hardware.tv.tuner.FrontendIsdbtPartialReceptionFlag;
 import android.hardware.tv.tuner.FrontendModulation;
 import android.hardware.tv.tuner.FrontendModulationStatus;
 import android.hardware.tv.tuner.FrontendRollOff;
@@ -190,4 +192,14 @@
      * If short frames is enabled or not.
      */
     boolean isShortFrames;
+
+    /**
+     * ISDB-T Mode.
+     */
+    FrontendIsdbtMode isdbtMode;
+
+    /**
+     * ISDB-T Partial Reception Flag.
+     */
+    FrontendIsdbtPartialReceptionFlag partialReceptionFlag;
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatusType.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatusType.aidl
index 93b75b0..103a4ab 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatusType.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/FrontendStatusType.aidl
@@ -208,4 +208,14 @@
      * If short frames is enabled or not.
      */
     IS_SHORT_FRAMES,
+
+    /**
+     * ISDB-T Mode.
+     */
+    ISDBT_MODE,
+
+    /**
+     * ISDB-T Partial Reception Flag.
+     */
+    ISDBT_PARTIAL_RECEPTION_FLAG,
 }
diff --git a/tv/tuner/aidl/android/hardware/tv/tuner/IFilter.aidl b/tv/tuner/aidl/android/hardware/tv/tuner/IFilter.aidl
index 63fe1dd..28927d1 100644
--- a/tv/tuner/aidl/android/hardware/tv/tuner/IFilter.aidl
+++ b/tv/tuner/aidl/android/hardware/tv/tuner/IFilter.aidl
@@ -16,14 +16,14 @@
 
 package android.hardware.tv.tuner;
 
+import android.hardware.common.NativeHandle;
 import android.hardware.common.fmq.MQDescriptor;
 import android.hardware.common.fmq.SynchronizedReadWrite;
-import android.hardware.common.NativeHandle;
-
-import android.hardware.tv.tuner.DemuxFilterSettings;
-import android.hardware.tv.tuner.IFilter;
 import android.hardware.tv.tuner.AvStreamType;
 import android.hardware.tv.tuner.DemuxFilterMonitorEventType;
+import android.hardware.tv.tuner.DemuxFilterSettings;
+import android.hardware.tv.tuner.FilterDelayHint;
+import android.hardware.tv.tuner.IFilter;
 
 /**
  * The Filter is used to filter wanted data according to the filter's
@@ -187,4 +187,13 @@
      * use demux as data source if the filter instance is NULL.
      */
     void setDataSource(in IFilter filter);
+
+    /**
+     * Set a delay hint.
+     *
+     * A delay hint should be used by the filter to rate limit calls to on
+     * FilterCallback.onFilterEvent by aggregating data according to the hint's
+     * specification.
+     */
+    void setDelayHint(in FilterDelayHint hint);
 }
diff --git a/tv/tuner/aidl/default/Filter.cpp b/tv/tuner/aidl/default/Filter.cpp
index bf89d12..5b6debb 100644
--- a/tv/tuner/aidl/default/Filter.cpp
+++ b/tv/tuner/aidl/default/Filter.cpp
@@ -34,16 +34,135 @@
 
 #define WAIT_TIMEOUT 3000000000
 
-Filter::Filter() {}
+FilterCallbackScheduler::FilterCallbackScheduler(const std::shared_ptr<IFilterCallback>& cb)
+    : mCallback(cb), mDataLength(0), mTimeDelayInMs(0), mDataSizeDelayInBytes(0) {
+    start();
+}
+
+FilterCallbackScheduler::~FilterCallbackScheduler() {
+    stop();
+}
+
+void FilterCallbackScheduler::onFilterEvent(DemuxFilterEvent&& event) {
+    std::lock_guard<std::mutex> lock(mLock);
+    mCallbackBuffer.push_back(std::move(event));
+    mDataLength += getDemuxFilterEventDataLength(event);
+
+    if (mDataLength >= mDataSizeDelayInBytes) {
+        // size limit has been reached, send out events
+        mCv.notify_all();
+    }
+}
+
+void FilterCallbackScheduler::onFilterStatus(const DemuxFilterStatus& status) {
+    if (mCallback) {
+        mCallback->onFilterStatus(status);
+    }
+}
+
+void FilterCallbackScheduler::setTimeDelayHint(int timeDelay) {
+    // updating the setTimeDelay does not go into effect until the condition
+    // variable times out or is notified.
+    // One possibility is to notify the condition variable right away when the
+    // time delay changes, but I don't see the benefit over waiting for the next
+    // timeout / push, since -- in any case -- this will not change very often.
+    mTimeDelayInMs = timeDelay;
+}
+
+void FilterCallbackScheduler::setDataSizeDelayHint(int dataSizeDelay) {
+    // similar to updating the time delay hint, when mDataSizeDelayInBytes
+    // changes, this will not go into immediate effect, but will wait until the
+    // next filterEvent.
+    // We could technically check the current data length and notify the
+    // condition variable if we wanted to, but again, this may be overkill.
+    mDataSizeDelayInBytes = dataSizeDelay;
+}
+
+bool FilterCallbackScheduler::hasCallbackRegistered() const {
+    return mCallback != nullptr;
+}
+
+void FilterCallbackScheduler::start() {
+    mIsRunning = true;
+    mCallbackThread = std::thread(&FilterCallbackScheduler::threadLoop, this);
+}
+
+void FilterCallbackScheduler::stop() {
+    mIsRunning = false;
+    if (mCallbackThread.joinable()) {
+        mCv.notify_all();
+        mCallbackThread.join();
+    }
+}
+
+void FilterCallbackScheduler::threadLoop() {
+    while (mIsRunning) {
+        threadLoopOnce();
+    }
+}
+
+void FilterCallbackScheduler::threadLoopOnce() {
+    std::unique_lock<std::mutex> lock(mLock);
+    // mTimeDelayInMs is an atomic value, so it should be copied into a local
+    // variable before use (to make sure both the if statement and wait_for use
+    // the same value).
+    int timeDelayInMs = mTimeDelayInMs;
+    if (timeDelayInMs > 0) {
+        mCv.wait_for(lock, std::chrono::milliseconds(timeDelayInMs));
+    } else {
+        // no reason to timeout, just wait until main thread determines it's
+        // okay to send data.
+        mCv.wait(lock);
+    }
+
+    // condition_variable wait locks mutex on timeout / notify
+    // Note: if stop() has been called in the meantime, do not send more filter
+    // events.
+    if (mIsRunning && !mCallbackBuffer.empty()) {
+        if (mCallback) {
+            mCallback->onFilterEvent(mCallbackBuffer);
+        }
+        mCallbackBuffer.clear();
+        mDataLength = 0;
+    }
+    lock.unlock();
+}
+
+int FilterCallbackScheduler::getDemuxFilterEventDataLength(const DemuxFilterEvent& event) {
+    // there is a risk that dataLength could be a negative value, but it
+    // *should* be safe to assume that it is always positive.
+    switch (event.getTag()) {
+        case DemuxFilterEvent::Tag::section:
+            return event.get<DemuxFilterEvent::Tag::section>().dataLength;
+        case DemuxFilterEvent::Tag::media:
+            return event.get<DemuxFilterEvent::Tag::media>().dataLength;
+        case DemuxFilterEvent::Tag::pes:
+            return event.get<DemuxFilterEvent::Tag::pes>().dataLength;
+        case DemuxFilterEvent::Tag::download:
+            return event.get<DemuxFilterEvent::Tag::download>().dataLength;
+        case DemuxFilterEvent::Tag::ipPayload:
+            return event.get<DemuxFilterEvent::Tag::ipPayload>().dataLength;
+
+        case DemuxFilterEvent::Tag::tsRecord:
+        case DemuxFilterEvent::Tag::mmtpRecord:
+        case DemuxFilterEvent::Tag::temi:
+        case DemuxFilterEvent::Tag::monitorEvent:
+        case DemuxFilterEvent::Tag::startId:
+            // these events do not include a payload and should therefore return
+            // 0.
+            // do not add a default option, so this will not compile when new types
+            // are added.
+            return 0;
+    }
+}
 
 Filter::Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
-               const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux) {
-    mType = type;
-    mFilterId = filterId;
-    mBufferSize = bufferSize;
-    mDemux = demux;
-    mCallback = cb;
-
+               const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux)
+    : mDemux(demux),
+      mCallbackScheduler(cb),
+      mFilterId(filterId),
+      mBufferSize(bufferSize),
+      mType(type) {
     switch (mType.mainType) {
         case DemuxFilterMainType::TS:
             if (mType.subType.get<DemuxFilterSubType::Tag::tsFilterType>() ==
@@ -111,6 +230,34 @@
     return ::ndk::ScopedAStatus::ok();
 }
 
+::ndk::ScopedAStatus Filter::setDelayHint(const FilterDelayHint& in_hint) {
+    if (mIsMediaFilter) {
+        // delay hint is not supported for media filters
+        return ::ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int32_t>(Result::UNAVAILABLE));
+    }
+
+    ALOGV("%s", __FUNCTION__);
+    if (in_hint.hintValue < 0) {
+        return ::ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int32_t>(Result::INVALID_ARGUMENT));
+    }
+
+    switch (in_hint.hintType) {
+        case FilterDelayHintType::TIME_DELAY_IN_MS:
+            mCallbackScheduler.setTimeDelayHint(in_hint.hintValue);
+            break;
+        case FilterDelayHintType::DATA_SIZE_DELAY_IN_BYTES:
+            mCallbackScheduler.setDataSizeDelayHint(in_hint.hintValue);
+            break;
+        default:
+            return ::ndk::ScopedAStatus::fromServiceSpecificError(
+                    static_cast<int32_t>(Result::INVALID_ARGUMENT));
+    }
+
+    return ::ndk::ScopedAStatus::ok();
+}
+
 ::ndk::ScopedAStatus Filter::getQueueDesc(MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) {
     ALOGV("%s", __FUNCTION__);
 
@@ -147,40 +294,36 @@
 ::ndk::ScopedAStatus Filter::start() {
     ALOGV("%s", __FUNCTION__);
     mFilterThreadRunning = true;
-    vector<DemuxFilterEvent> events;
+    std::vector<DemuxFilterEvent> events;
     // All the filter event callbacks in start are for testing purpose.
     switch (mType.mainType) {
         case DemuxFilterMainType::TS:
             createMediaEvent(events);
-            mCallback->onFilterEvent(events);
             createTsRecordEvent(events);
-            mCallback->onFilterEvent(events);
             createTemiEvent(events);
-            mCallback->onFilterEvent(events);
             break;
         case DemuxFilterMainType::MMTP:
             createDownloadEvent(events);
-            mCallback->onFilterEvent(events);
             createMmtpRecordEvent(events);
-            mCallback->onFilterEvent(events);
             break;
         case DemuxFilterMainType::IP:
             createSectionEvent(events);
-            mCallback->onFilterEvent(events);
             createIpPayloadEvent(events);
-            mCallback->onFilterEvent(events);
             break;
         case DemuxFilterMainType::TLV:
             createMonitorEvent(events);
-            mCallback->onFilterEvent(events);
             break;
         case DemuxFilterMainType::ALP:
             createMonitorEvent(events);
-            mCallback->onFilterEvent(events);
             break;
         default:
             break;
     }
+
+    for (auto&& event : events) {
+        mCallbackScheduler.onFilterEvent(std::move(event));
+    }
+
     return startFilterLoop();
 }
 
@@ -319,15 +462,14 @@
     if (newScramblingStatus ^ mScramblingStatusMonitored) {
         mScramblingStatusMonitored = newScramblingStatus;
         if (mScramblingStatusMonitored) {
-            if (mCallback != nullptr) {
+            if (mCallbackScheduler.hasCallbackRegistered()) {
                 // Assuming current status is always NOT_SCRAMBLED
-                vector<DemuxFilterEvent> events;
-                DemuxFilterMonitorEvent monitorEvent;
-                events.resize(1);
-                monitorEvent.set<DemuxFilterMonitorEvent::Tag::scramblingStatus>(
+                auto monitorEvent = DemuxFilterMonitorEvent::make<
+                        DemuxFilterMonitorEvent::Tag::scramblingStatus>(
                         ScramblingStatus::NOT_SCRAMBLED);
-                events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
-                mCallback->onFilterEvent(events);
+                auto event =
+                        DemuxFilterEvent::make<DemuxFilterEvent::Tag::monitorEvent>(monitorEvent);
+                mCallbackScheduler.onFilterEvent(std::move(event));
             } else {
                 return ::ndk::ScopedAStatus::fromServiceSpecificError(
                         static_cast<int32_t>(Result::INVALID_STATE));
@@ -339,14 +481,13 @@
     if (newIpCid ^ mIpCidMonitored) {
         mIpCidMonitored = newIpCid;
         if (mIpCidMonitored) {
-            if (mCallback != nullptr) {
+            if (mCallbackScheduler.hasCallbackRegistered()) {
                 // Return random cid
-                vector<DemuxFilterEvent> events;
-                DemuxFilterMonitorEvent monitorEvent;
-                events.resize(1);
-                monitorEvent.set<DemuxFilterMonitorEvent::Tag::cid>(1);
-                events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
-                mCallback->onFilterEvent(events);
+                auto monitorEvent =
+                        DemuxFilterMonitorEvent::make<DemuxFilterMonitorEvent::Tag::cid>(1);
+                auto event =
+                        DemuxFilterEvent::make<DemuxFilterEvent::Tag::monitorEvent>(monitorEvent);
+                mCallbackScheduler.onFilterEvent(std::move(event));
             } else {
                 return ::ndk::ScopedAStatus::fromServiceSpecificError(
                         static_cast<int32_t>(Result::INVALID_STATE));
@@ -402,26 +543,26 @@
         }
 
         // After successfully write, send a callback and wait for the read to be done
-        if (mCallback != nullptr) {
+        if (mCallbackScheduler.hasCallbackRegistered()) {
             if (mConfigured) {
-                vector<DemuxFilterEvent> startEvent;
-                startEvent.resize(1);
-                startEvent[0].set<DemuxFilterEvent::Tag::startId>(mStartId++);
-                mCallback->onFilterEvent(startEvent);
+                auto startEvent =
+                        DemuxFilterEvent::make<DemuxFilterEvent::Tag::startId>(mStartId++);
+                mCallbackScheduler.onFilterEvent(std::move(startEvent));
                 mConfigured = false;
             }
-            mCallback->onFilterEvent(mFilterEvents);
+
+            for (auto&& event : mFilterEvents) {
+                mCallbackScheduler.onFilterEvent(std::move(event));
+            }
         } else {
             ALOGD("[Filter] filter callback is not configured yet.");
             mFilterThreadRunning = false;
             return;
         }
 
-        mFilterEvents.resize(0);
+        mFilterEvents.clear();
         mFilterStatus = DemuxFilterStatus::DATA_READY;
-        if (mCallback != nullptr) {
-            mCallback->onFilterStatus(mFilterStatus);
-        }
+        mCallbackScheduler.onFilterStatus(mFilterStatus);
         break;
     }
 
@@ -452,10 +593,10 @@
                     continue;
                 }
                 // After successfully write, send a callback and wait for the read to be done
-                if (mCallback != nullptr) {
-                    mCallback->onFilterEvent(mFilterEvents);
+                for (auto&& event : mFilterEvents) {
+                    mCallbackScheduler.onFilterEvent(std::move(event));
                 }
-                mFilterEvents.resize(0);
+                mFilterEvents.clear();
                 break;
             }
             // We do not wait for the last read to be done
@@ -491,9 +632,7 @@
     DemuxFilterStatus newStatus = checkFilterStatusChange(
             availableToWrite, availableToRead, ceil(fmqSize * 0.75), ceil(fmqSize * 0.25));
     if (mFilterStatus != newStatus) {
-        if (mCallback != nullptr) {
-            mCallback->onFilterStatus(newStatus);
-        }
+        mCallbackScheduler.onFilterStatus(newStatus);
         mFilterStatus = newStatus;
     }
 }
@@ -649,9 +788,7 @@
             ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength);
         }
 
-        int size = mFilterEvents.size();
-        mFilterEvents.resize(size + 1);
-        mFilterEvents[size].set<DemuxFilterEvent::Tag::pes>(pesEvent);
+        mFilterEvents.push_back(DemuxFilterEvent::make<DemuxFilterEvent::Tag::pes>(pesEvent));
         mPesOutput.clear();
     }
 
@@ -755,11 +892,7 @@
             .firstMbInSlice = 0,  // random address
     };
 
-    int size;
-    size = mFilterEvents.size();
-    mFilterEvents.resize(size + 1);
-    mFilterEvents[size].set<DemuxFilterEvent::Tag::tsRecord>(recordEvent);
-
+    mFilterEvents.push_back(DemuxFilterEvent::make<DemuxFilterEvent::Tag::tsRecord>(recordEvent));
     mRecordFilterOutput.clear();
     return ::ndk::ScopedAStatus::ok();
 }
@@ -781,8 +914,6 @@
     if (!writeDataToFilterMQ(data)) {
         return false;
     }
-    int size = mFilterEvents.size();
-    mFilterEvents.resize(size + 1);
     DemuxFilterSectionEvent secEvent;
     secEvent = {
             // temp dump meta data
@@ -791,7 +922,7 @@
             .sectionNum = 1,
             .dataLength = static_cast<int32_t>(data.size()),
     };
-    mFilterEvents[size].set<DemuxFilterEvent::Tag::section>(secEvent);
+    mFilterEvents.push_back(DemuxFilterEvent::make<DemuxFilterEvent::Tag::section>(secEvent));
     return true;
 }
 
@@ -880,19 +1011,16 @@
     mDataId2Avfd[dataId] = dup(av_fd);
 
     // Create mediaEvent and send callback
-    int size = mFilterEvents.size();
-    mFilterEvents.resize(size + 1);
-
-    mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
-            ::android::dupToAidl(nativeHandle);
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
-            static_cast<int64_t>(output.size());
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avDataId = static_cast<int64_t>(dataId);
+    auto event = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+    auto& mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
+    mediaEvent.avMemory = ::android::dupToAidl(nativeHandle);
+    mediaEvent.dataLength = static_cast<int64_t>(output.size());
+    mediaEvent.avDataId = static_cast<int64_t>(dataId);
     if (mPts) {
-        mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+        mediaEvent.pts = mPts;
         mPts = 0;
     }
+    mFilterEvents.push_back(std::move(event));
 
     // Clear and log
     native_handle_close(nativeHandle);
@@ -923,18 +1051,17 @@
     }
 
     // Create mediaEvent and send callback
-    int size = mFilterEvents.size();
-    mFilterEvents.resize(size + 1);
-    mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
-            ::android::dupToAidl(nativeHandle);
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().offset = mSharedAvMemOffset;
-    mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
-            static_cast<int64_t>(output.size());
+    auto event = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+    auto& mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
+    mediaEvent.avMemory = ::android::dupToAidl(nativeHandle);
+    mediaEvent.offset = mSharedAvMemOffset;
+    mediaEvent.dataLength = static_cast<int64_t>(output.size());
     if (mPts) {
-        mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+        mediaEvent.pts = mPts;
         mPts = 0;
     }
+    mFilterEvents.push_back(std::move(event));
+
     mSharedAvMemOffset += output.size();
 
     // Clear and log
diff --git a/tv/tuner/aidl/default/Filter.h b/tv/tuner/aidl/default/Filter.h
index 2ca25af..a5adf4c 100644
--- a/tv/tuner/aidl/default/Filter.h
+++ b/tv/tuner/aidl/default/Filter.h
@@ -18,6 +18,8 @@
 
 #include <aidl/android/hardware/tv/tuner/BnFilter.h>
 #include <aidl/android/hardware/tv/tuner/Constant.h>
+#include <aidl/android/hardware/tv/tuner/DemuxFilterEvent.h>
+#include <aidl/android/hardware/tv/tuner/DemuxFilterStatus.h>
 
 #include <fmq/AidlMessageQueue.h>
 #include <inttypes.h>
@@ -25,6 +27,7 @@
 #include <math.h>
 #include <sys/stat.h>
 #include <atomic>
+#include <condition_variable>
 #include <set>
 #include <thread>
 
@@ -52,10 +55,49 @@
 class Demux;
 class Dvr;
 
-class Filter : public BnFilter {
+class FilterCallbackScheduler final {
   public:
-    Filter();
+    FilterCallbackScheduler(const std::shared_ptr<IFilterCallback>& cb);
+    ~FilterCallbackScheduler();
 
+    void onFilterEvent(DemuxFilterEvent&& event);
+    void onFilterStatus(const DemuxFilterStatus& status);
+
+    void setTimeDelayHint(int timeDelay);
+    void setDataSizeDelayHint(int dataSizeDelay);
+
+    bool hasCallbackRegistered() const;
+
+  private:
+    void start();
+    void stop();
+
+    void threadLoop();
+    void threadLoopOnce();
+
+    static int getDemuxFilterEventDataLength(const DemuxFilterEvent& event);
+
+  private:
+    std::shared_ptr<IFilterCallback> mCallback;
+    std::thread mCallbackThread;
+    std::atomic<bool> mIsRunning;
+
+    // mLock protects mCallbackBuffer, mCv, and mDataLength
+    std::mutex mLock;
+    std::vector<DemuxFilterEvent> mCallbackBuffer;
+    std::condition_variable mCv;
+    int mDataLength;
+
+    // both of these need to be atomic (not just mTimeDelayInMs) as this class
+    // needs to be threadsafe.
+    std::atomic<int> mTimeDelayInMs;
+    std::atomic<int> mDataSizeDelayInBytes;
+};
+
+class Filter : public BnFilter {
+    friend class FilterCallbackScheduler;
+
+  public:
     Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
            const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux);
 
@@ -78,6 +120,7 @@
     ::ndk::ScopedAStatus releaseAvHandle(const NativeHandle& in_avMemory,
                                          int64_t in_avDataId) override;
     ::ndk::ScopedAStatus setDataSource(const std::shared_ptr<IFilter>& in_filter) override;
+    ::ndk::ScopedAStatus setDelayHint(const FilterDelayHint& in_hint) override;
 
     /**
      * To create a FilterMQ and its Event Flag.
@@ -103,10 +146,8 @@
     std::shared_ptr<Demux> mDemux;
     // Dvr reference once the filter is attached to any
     std::shared_ptr<Dvr> mDvr = nullptr;
-    /**
-     * Filter callbacks used on filter events or FMQ status
-     */
-    std::shared_ptr<IFilterCallback> mCallback = nullptr;
+
+    FilterCallbackScheduler mCallbackScheduler;
 
     int64_t mFilterId;
     int32_t mCid = static_cast<int32_t>(Constant::INVALID_IP_FILTER_CONTEXT_ID);
diff --git a/tv/tuner/aidl/default/Frontend.cpp b/tv/tuner/aidl/default/Frontend.cpp
index f9e697e..77d20e2 100644
--- a/tv/tuner/aidl/default/Frontend.cpp
+++ b/tv/tuner/aidl/default/Frontend.cpp
@@ -607,6 +607,13 @@
                         status.set<FrontendStatus::interleaving>(interleaves);
                         break;
                     }
+                    case FrontendType::ISDBT: {
+                        interleave.set<FrontendInterleaveMode::Tag::isdbt>(
+                                FrontendIsdbtTimeInterleaveMode::INTERLEAVE_1_0); // value = 1 << 1
+                        interleaves.push_back(interleave);
+                        status.set<FrontendStatus::interleaving>(interleaves);
+                        break;
+                    }
                     default:
                         break;
                 }
@@ -660,6 +667,15 @@
                 status.set<FrontendStatus::isShortFrames>(true);
                 break;
             }
+            case FrontendStatusType::ISDBT_MODE: {
+                status.set<FrontendStatus::isdbtMode>(FrontendIsdbtMode::AUTO);
+                break;
+            }
+            case FrontendStatusType::ISDBT_PARTIAL_RECEPTION_FLAG: {
+                status.set<FrontendStatus::partialReceptionFlag>(
+                        FrontendIsdbtPartialReceptionFlag::AUTO);
+                break;
+            }
             default: {
                 continue;
             }
diff --git a/tv/tuner/aidl/default/Tuner.cpp b/tv/tuner/aidl/default/Tuner.cpp
index 8468929..badb08f 100644
--- a/tv/tuner/aidl/default/Tuner.cpp
+++ b/tv/tuner/aidl/default/Tuner.cpp
@@ -119,6 +119,10 @@
             .coderateCap = (int)FrontendIsdbtCoderate::CODERATE_4_5 |
                            (int)FrontendIsdbtCoderate::CODERATE_6_7,
             .guardIntervalCap = (int)FrontendIsdbtGuardInterval::INTERVAL_1_128,
+            .timeInterleaveCap = (int)FrontendIsdbtTimeInterleaveMode::AUTO |
+                                 (int)FrontendIsdbtTimeInterleaveMode::INTERLEAVE_1_0,
+            .isSegmentAuto = true,
+            .isFullSegment = true,
     };
     capsIsdbt.set<FrontendCapabilities::Tag::isdbtCaps>(isdbtCaps);
     mFrontendCaps[5] = capsIsdbt;
@@ -131,6 +135,9 @@
             FrontendStatusType::GUARD_INTERVAL,
             FrontendStatusType::TRANSMISSION_MODE,
             FrontendStatusType::ISDBT_SEGMENTS,
+            FrontendStatusType::ISDBT_MODE,
+            FrontendStatusType::ISDBT_PARTIAL_RECEPTION_FLAG,
+            FrontendStatusType::INTERLEAVINGS,
     };
     mFrontendStatusCaps[5] = statusCaps;
 
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.h b/tv/tuner/aidl/vts/functional/FilterTests.h
index 91a0a4a..c965d95 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.h
+++ b/tv/tuner/aidl/vts/functional/FilterTests.h
@@ -74,7 +74,6 @@
 
     void setFilterId(int32_t filterId) { mFilterId = filterId; }
     void setFilterInterface(std::shared_ptr<IFilter> filter) { mFilter = filter; }
-    void setFilterEventType(FilterEventType type) { mFilterEventType = type; }
     void setSharedHandle(native_handle_t* sharedHandle) { mAvSharedHandle = sharedHandle; }
     void setMemSize(uint64_t size) { mAvSharedMemSize = size; }
 
@@ -89,7 +88,6 @@
   private:
     int32_t mFilterId;
     std::shared_ptr<IFilter> mFilter;
-    FilterEventType mFilterEventType;
 
     native_handle_t* mAvSharedHandle = nullptr;
     uint64_t mAvSharedMemSize = -1;
diff --git a/tv/tuner/aidl/vts/functional/FrontendTests.cpp b/tv/tuner/aidl/vts/functional/FrontendTests.cpp
index 77add8e..f1c3595 100644
--- a/tv/tuner/aidl/vts/functional/FrontendTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FrontendTests.cpp
@@ -387,6 +387,16 @@
                             expectStatuses[i].get<FrontendStatus::Tag::isShortFrames>());
                 break;
             }
+            case FrontendStatusType::ISDBT_MODE: {
+                ASSERT_TRUE(realStatuses[i].get<FrontendStatus::Tag::isdbtMode>() ==
+                            expectStatuses[i].get<FrontendStatus::Tag::isdbtMode>());
+                break;
+            }
+            case FrontendStatusType::ISDBT_PARTIAL_RECEPTION_FLAG: {
+                ASSERT_TRUE(realStatuses[i].get<FrontendStatus::Tag::partialReceptionFlag>() ==
+                            expectStatuses[i].get<FrontendStatus::Tag::partialReceptionFlag>());
+                break;
+            }
             default: {
                 continue;
             }
diff --git a/uwb/aidl/Android.bp b/uwb/aidl/Android.bp
index 0d1ebc2..a1a66e5 100755
--- a/uwb/aidl/Android.bp
+++ b/uwb/aidl/Android.bp
@@ -53,9 +53,6 @@
             ],
         },
         ndk: {
-            vndk: {
-                enabled: true,
-            },
             apex_available: [
                 "//apex_available:platform",
                 "com.android.uwb",
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl b/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl
similarity index 82%
copy from automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
copy to uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl
index 2b872ab..774133e 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/UserFlags.aidl
+++ b/uwb/aidl/aidl_api/android.hardware.uwb.fira_android/current/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl
@@ -2,10 +2,10 @@
  * 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 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
+ *        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,
@@ -31,14 +31,8 @@
 // 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 UserFlags {
-  NONE = 0,
-  SYSTEM = 1,
-  GUEST = 2,
-  EPHEMERAL = 4,
-  ADMIN = 8,
-  DISABLED = 16,
-  PROFILE = 32,
+package android.hardware.uwb.fira_android;
+@Backing(type="long") @VintfStability
+enum UwbAndroidCapabilities {
+  POWER_STATS_QUERY = 1,
 }
diff --git a/uwb/aidl/aidl_api/android.hardware.uwb/current/android/hardware/uwb/IUwbChip.aidl b/uwb/aidl/aidl_api/android.hardware.uwb/current/android/hardware/uwb/IUwbChip.aidl
index 7074753..c4cb47b 100644
--- a/uwb/aidl/aidl_api/android.hardware.uwb/current/android/hardware/uwb/IUwbChip.aidl
+++ b/uwb/aidl/aidl_api/android.hardware.uwb/current/android/hardware/uwb/IUwbChip.aidl
@@ -40,6 +40,7 @@
   void open(in android.hardware.uwb.IUwbClientCallback clientCallback);
   void close();
   void coreInit();
-  int getSupportedVendorUciVersion();
+  int getSupportedAndroidUciVersion();
+  long getSupportedAndroidCapabilities();
   int sendUciMessage(in byte[] data);
 }
diff --git a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
old mode 100755
new mode 100644
index 9530af4..0c98611
--- a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
+++ b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
@@ -49,14 +49,24 @@
      */
     void coreInit();
 
-     /**
-      * Supported version of vendor UCI specification.
-      *
-      * This corresponds to the version of the "android.hardware.uwb.fira_android" types-only
-      * package included in the HAL implementation. This vendor params/commands package will be
-      * updated on a different cadence to the main UWB HAL interface package.
-      */
-    int getSupportedVendorUciVersion();
+    /**
+     * Supported version of vendor UCI specification.
+     *
+     * @return Returns the version of the "android.hardware.uwb.fira_android" types-only
+     * package included in the HAL implementation. This vendor params/commands package will be
+     * updated on a different cadence to the main UWB HAL interface package.
+     */
+    int getSupportedAndroidUciVersion();
+
+    /**
+     * Mechanism to allow HAL implementation to optionally expose features that are defined
+     * in the "android.hardware.uwb.fira_android" types-only package.
+     *
+     * @return Returns the bitmask of capabilities
+     * (android.hardware.uwb.fira_android.UwbAndroidCapabilities) that is supported by the
+     * HAL implementation.
+     */
+    long getSupportedAndroidCapabilities();
 
     /**
      * Write the UCI message to the UWB Subsystem.
diff --git a/uwb/aidl/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl b/uwb/aidl/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl
new file mode 100644
index 0000000..3486d5a
--- /dev/null
+++ b/uwb/aidl/android/hardware/uwb/fira_android/UwbAndroidCapabilities.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package android.hardware.uwb.fira_android;
+
+/**
+ * Android specific capabilities should be defined here.
+ *
+ * For any features enabled via the FIRA vendor commands for Android, use this bitmask
+ * to allow devices to expose the features supported by the HAL implementation.
+ *
+ */
+@VintfStability
+@Backing(type="long")
+enum UwbAndroidCapabilities {
+    /** TODO: Change the name if necessary when the corresponding vendor commands are added */
+    POWER_STATS_QUERY = 0x1,
+}
diff --git a/uwb/aidl/default/uwb_chip.cpp b/uwb/aidl/default/uwb_chip.cpp
index fe64fa7..10dbdb6 100644
--- a/uwb/aidl/default/uwb_chip.cpp
+++ b/uwb/aidl/default/uwb_chip.cpp
@@ -17,7 +17,8 @@
 #include "uwb.h"
 
 namespace {
-constexpr static int kVendorUciVersion = 1;
+constexpr static int32_t kAndroidUciVersion = 1;
+constexpr static int64_t kAndroidCapabilities = 0;
 }
 
 namespace android {
@@ -50,8 +51,13 @@
     return ndk::ScopedAStatus::ok();
 }
 
-::ndk::ScopedAStatus UwbChip::getSupportedVendorUciVersion(int32_t* version) {
-    *version = kVendorUciVersion;
+::ndk::ScopedAStatus UwbChip::getSupportedAndroidUciVersion(int32_t* version) {
+    *version = kAndroidUciVersion;
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus UwbChip::getSupportedAndroidCapabilities(int64_t* capabilities) {
+    *capabilities = kAndroidCapabilities;
     return ndk::ScopedAStatus::ok();
 }
 
diff --git a/uwb/aidl/default/uwb_chip.h b/uwb/aidl/default/uwb_chip.h
index ef1d5b6..ca97120 100644
--- a/uwb/aidl/default/uwb_chip.h
+++ b/uwb/aidl/default/uwb_chip.h
@@ -37,7 +37,8 @@
     ::ndk::ScopedAStatus open(const std::shared_ptr<IUwbClientCallback>& clientCallback) override;
     ::ndk::ScopedAStatus close() override;
     ::ndk::ScopedAStatus coreInit() override;
-    ::ndk::ScopedAStatus getSupportedVendorUciVersion(int32_t* version) override;
+    ::ndk::ScopedAStatus getSupportedAndroidUciVersion(int32_t* version) override;
+    ::ndk::ScopedAStatus getSupportedAndroidCapabilities(int64_t* capabilities) override;
     ::ndk::ScopedAStatus sendUciMessage(const std::vector<uint8_t>& data,
                                         int32_t* bytes_written) override;
 
diff --git a/uwb/aidl/vts/VtsHalUwbTargetTest.cpp b/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
index 9ac2678..3820c0f 100644
--- a/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
+++ b/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
@@ -166,15 +166,23 @@
     EXPECT_TRUE(iuwb_chip->coreInit().isOk());
 }
 
-TEST_P(UwbAidl, ChipGetSupportedVendorUciVersion) {
+TEST_P(UwbAidl, ChipGetSupportedAndroidUciVersion) {
     const auto iuwb_chip = getAnyChipAndOpen();
     EXPECT_TRUE(iuwb_chip->coreInit().isOk());
 
-    int version;
-    EXPECT_TRUE(iuwb_chip->getSupportedVendorUciVersion(&version).isOk());
+    int32_t version;
+    EXPECT_TRUE(iuwb_chip->getSupportedAndroidUciVersion(&version).isOk());
     EXPECT_GT(version, 0);
 }
 
+TEST_P(UwbAidl, ChipGetSupportedAndroidCapabilities) {
+    const auto iuwb_chip = getAnyChipAndOpen();
+    EXPECT_TRUE(iuwb_chip->coreInit().isOk());
+
+    int64_t capabilities;
+    EXPECT_TRUE(iuwb_chip->getSupportedAndroidCapabilities(&capabilities).isOk());
+}
+
 TEST_P(UwbAidl, ChipGetName) {
     std::string chip_name = getAnyChipName();
     std::shared_ptr<IUwbChip> iuwb_chip;
diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
index 53f8c0e..09cc21b 100644
--- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
@@ -417,6 +417,9 @@
 
             if (isPrimitiveSupported) {
                 EXPECT_EQ(Status::EX_NONE, status.exceptionCode());
+                if (primitive != CompositePrimitive::NOOP) {
+                    ASSERT_GT(duration, 0) << toString(primitive) << " " << duration;
+                }
             } else {
                 EXPECT_TRUE(isUnknownOrUnsupported(status)) << status;
             }
diff --git a/wifi/hostapd/aidl/vts/functional/Android.bp b/wifi/hostapd/aidl/vts/functional/Android.bp
index d37da08..e58cf5e 100644
--- a/wifi/hostapd/aidl/vts/functional/Android.bp
+++ b/wifi/hostapd/aidl/vts/functional/Android.bp
@@ -16,9 +16,10 @@
     srcs: ["VtsHalHostapdTargetTest.cpp"],
     shared_libs: [
         "libbinder",
+        "libbinder_ndk",
     ],
     static_libs: [
-        "android.hardware.wifi.hostapd-V1-cpp",
+        "android.hardware.wifi.hostapd-V1-ndk",
     ],
     test_suites: [
         "general-tests",
diff --git a/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp b/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
index 92fbf50..41c54b3 100644
--- a/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
+++ b/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
@@ -14,33 +14,33 @@
  * limitations under the License.
  */
 #include <VtsCoreUtil.h>
-
 #include <aidl/Gtest.h>
 #include <aidl/Vintf.h>
-#include <android/hardware/wifi/hostapd/BnHostapd.h>
+#include <aidl/android/hardware/wifi/hostapd/BnHostapd.h>
+#include <aidl/android/hardware/wifi/hostapd/BnHostapdCallback.h>
+#include <android/binder_manager.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 
+using aidl::android::hardware::wifi::hostapd::BandMask;
+using aidl::android::hardware::wifi::hostapd::BnHostapdCallback;
+using aidl::android::hardware::wifi::hostapd::ChannelParams;
+using aidl::android::hardware::wifi::hostapd::DebugLevel;
+using aidl::android::hardware::wifi::hostapd::EncryptionType;
+using aidl::android::hardware::wifi::hostapd::FrequencyRange;
+using aidl::android::hardware::wifi::hostapd::Ieee80211ReasonCode;
+using aidl::android::hardware::wifi::hostapd::IfaceParams;
+using aidl::android::hardware::wifi::hostapd::IHostapd;
+using aidl::android::hardware::wifi::hostapd::NetworkParams;
 using android::ProcessState;
-using android::sp;
-using android::String16;
-using android::hardware::wifi::hostapd::BandMask;
-using android::hardware::wifi::hostapd::ChannelParams;
-using android::hardware::wifi::hostapd::DebugLevel;
-using android::hardware::wifi::hostapd::EncryptionType;
-using android::hardware::wifi::hostapd::FrequencyRange;
-using android::hardware::wifi::hostapd::Ieee80211ReasonCode;
-using android::hardware::wifi::hostapd::IfaceParams;
-using android::hardware::wifi::hostapd::IHostapd;
-using android::hardware::wifi::hostapd::NetworkParams;
 
 namespace {
 const unsigned char kNwSsid[] = {'t', 'e', 's', 't', '1', '2', '3', '4', '5'};
-const String16 kIfaceName = String16("wlan0");
-const String16 kPassphrase = String16("test12345");
-const String16 kInvalidMinPassphrase = String16("test");
-const String16 kInvalidMaxPassphrase = String16(
-    "0123456789012345678901234567890123456789012345678901234567890123456789");
+const std::string kIfaceName = "wlan0";
+const std::string kPassphrase = "test12345";
+const std::string kInvalidMinPassphrase = "test";
+const std::string kInvalidMaxPassphrase =
+    "0123456789012345678901234567890123456789012345678901234567890123456789";
 const int kIfaceChannel = 6;
 const int kIfaceInvalidChannel = 567;
 const std::vector<uint8_t> kTestZeroMacAddr(6, 0x0);
@@ -56,8 +56,8 @@
 class HostapdAidl : public testing::TestWithParam<std::string> {
    public:
     virtual void SetUp() override {
-        hostapd = android::waitForDeclaredService<IHostapd>(
-            String16(GetParam().c_str()));
+        hostapd = IHostapd::fromBinder(ndk::SpAIBinder(
+            AServiceManager_waitForService(GetParam().c_str())));
         ASSERT_NE(hostapd, nullptr);
         EXPECT_TRUE(hostapd->setDebugParams(DebugLevel::EXCESSIVE).isOk());
         isAcsSupport = testing::checkSubstringInCommandOutput(
@@ -77,12 +77,12 @@
         sleep(3);
     }
 
-    sp<IHostapd> hostapd;
+    std::shared_ptr<IHostapd> hostapd;
     bool isAcsSupport;
     bool isWpa3SaeSupport;
     bool isBridgedSupport;
 
-    IfaceParams getIfaceParamsWithoutAcs(String16 iface_name) {
+    IfaceParams getIfaceParamsWithoutAcs(std::string iface_name) {
         IfaceParams iface_params;
         ChannelParams channelParams;
         std::vector<ChannelParams> vec_channelParams;
@@ -103,7 +103,7 @@
         return iface_params;
     }
 
-    IfaceParams getIfaceParamsWithBridgedModeACS(String16 iface_name) {
+    IfaceParams getIfaceParamsWithBridgedModeACS(std::string iface_name) {
         IfaceParams iface_params = getIfaceParamsWithoutAcs(iface_name);
         iface_params.channelParams[0].enableAcs = true;
         iface_params.channelParams[0].acsShouldExcludeDfs = true;
@@ -121,7 +121,7 @@
         return iface_params;
     }
 
-    IfaceParams getIfaceParamsWithAcs(String16 iface_name) {
+    IfaceParams getIfaceParamsWithAcs(std::string iface_name) {
         IfaceParams iface_params = getIfaceParamsWithoutAcs(iface_name);
         iface_params.channelParams[0].enableAcs = true;
         iface_params.channelParams[0].acsShouldExcludeDfs = true;
@@ -131,7 +131,7 @@
         return iface_params;
     }
 
-    IfaceParams getIfaceParamsWithAcsAndFreqRange(String16 iface_name) {
+    IfaceParams getIfaceParamsWithAcsAndFreqRange(std::string iface_name) {
         IfaceParams iface_params = getIfaceParamsWithAcs(iface_name);
         FrequencyRange freqRange;
         freqRange.startMhz = 2412;
@@ -143,7 +143,8 @@
         return iface_params;
     }
 
-    IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange(String16 iface_name) {
+    IfaceParams getIfaceParamsWithAcsAndInvalidFreqRange(
+        std::string iface_name) {
         IfaceParams iface_params =
             getIfaceParamsWithAcsAndFreqRange(iface_name);
         iface_params.channelParams[0].acsChannelFreqRangesMhz[0].startMhz =
@@ -153,7 +154,7 @@
         return iface_params;
     }
 
-    IfaceParams getIfaceParamsWithInvalidChannel(String16 iface_name) {
+    IfaceParams getIfaceParamsWithInvalidChannel(std::string iface_name) {
         IfaceParams iface_params = getIfaceParamsWithoutAcs(iface_name);
         iface_params.channelParams[0].channel = kIfaceInvalidChannel;
         return iface_params;
@@ -215,11 +216,37 @@
     NetworkParams getInvalidSaeNwParams() {
         NetworkParams nw_params = getOpenNwParams();
         nw_params.encryptionType = EncryptionType::WPA3_SAE;
-        nw_params.passphrase = String16("");
+        nw_params.passphrase = "";
         return nw_params;
     }
 };
 
+class HostapdCallback : public BnHostapdCallback {
+   public:
+    HostapdCallback() = default;
+    ::ndk::ScopedAStatus onApInstanceInfoChanged(
+        const ::aidl::android::hardware::wifi::hostapd::ApInfo &) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onConnectedClientsChanged(
+        const ::aidl::android::hardware::wifi::hostapd::ClientInfo &) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onFailure(const std::string &) override {
+        return ndk::ScopedAStatus::ok();
+    }
+};
+
+/**
+ * Register callback
+ */
+TEST_P(HostapdAidl, RegisterCallback) {
+    std::shared_ptr<HostapdCallback> callback =
+        ndk::SharedRefBase::make<HostapdCallback>();
+    ASSERT_NE(callback, nullptr);
+    EXPECT_TRUE(hostapd->registerCallback(callback).isOk());
+}
+
 /**
  * Adds an access point with PSK network config & ACS enabled.
  * Access point creation should pass.